Python Programming

برمجة الخيوط والطوابير في بايثون: إتقان المهام المتزامنة

Spread the love

توفر بايثون إمكانيات قوية للخيوط لتعزيز أداء التطبيقات من خلال تنفيذ المهام المتزامنة. ومع ذلك، فإن الخيوط غير المنضبطة قد تؤدي إلى تنافس على الموارد وعدم الكفاءة. تستعرض هذه المقالة تقنيات الخيوط الفعالة باستخدام قوائم الانتظار في بايثون، مع التركيز على منع المشاكل الشائعة وزيادة الأداء.

محتويات

الخيوط في بايثون

تمكّن خيوط بايثون من التنفيذ المتزامن على ما يبدو لوظائف متعددة. هذا مفيد بشكل خاص لعمليات مرتبطة بالمدخلات/المخرجات (طلبات الشبكة، ومعالجة الملفات)، حيث يمكن للخيط الانتظار للموارد الخارجية دون حجب الآخرين. ومع ذلك، فإن قفل المُفسّر العالمي (GIL) في CPython يحد من التوازي الحقيقي للمهام المرتبطة بوحدة المعالجة المركزية؛ حيث يمكن لخيط واحد فقط التحكم في مُفسّر بايثون في أي وقت. لذلك، يتم تحقيق فعالية الخيوط بشكل أساسي مع عمليات مرتبطة بالمدخلات/المخرجات.

ضع في اعتبارك هذا المثال للخيوط البسيطة بدون قائمة انتظار:


import threading
import time

def worker(name):
    print(f"Thread {name}: starting")
    time.sleep(2)  # Simulate I/O-bound operation
    print(f"Thread {name}: finishing")

threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print("All threads finished")

يُنشئ هذا خمسة خيوط، كل منها يُشغّل دالة worker. على الرغم من كونه وظيفياً، إلا أنه يفتقر إلى التحكم في عدد الخيوط المتزامنة، مما قد يُثقل النظام بمهام عديدة.

إدارة الخيوط باستخدام قوائم الانتظار

للتحكم في تنفيذ الخيوط المتزامنة ومنع استنفاد الموارد، استخدم queue.Queue. تعمل قائمة الانتظار كوسيط، وتدير المهام بين مجموعة الخيوط والمعالجة. تسترد الخيوط المهام باستمرار، وتُعالِج حتى تصبح قائمة الانتظار فارغة. يُنظّم هذا النهج التزامن ويدير الموارد بكفاءة.

فيما يلي مثال محسّن يستخدم queue.Queue:


import threading
import time
import queue

def worker(q):
    while True:
        try:
            item = q.get(True, 1)  # Block for 1 second, raise exception if empty
            print(f"Thread {threading.current_thread().name}: processing {item}")
            time.sleep(2)  # Simulate I/O-bound operation
            print(f"Thread {threading.current_thread().name}: finished {item}")
            q.task_done()
        except queue.Empty:
            break

q = queue.Queue()
num_threads = 3  # Control concurrent threads
for i in range(10):  # Number of tasks
    q.put(i)

threads = []
for i in range(num_threads):
    t = threading.Thread(target=worker, args=(q,), daemon=True) # Daemon threads exit when main thread exits
    threads.append(t)
    t.start()

q.join()  # Wait for all queue items to be processed

print("All tasks finished")

يستخدم هذا المثال queue.Queue لحفظ المهام (0-9). لا يعمل سوى ثلاثة خيوط بشكل متزامن، حيث يتم سحبها من قائمة الانتظار. q.join() يضمن انتظار الخيط الرئيسي لإكمال المهمة. daemon=True يجعل خيوط عامل الخدمة تخرج عند انتهاء الخيط الرئيسي، مما يمنع التعليق.

اختيار النهج المناسب: الخيوط مقابل المعالجة المتعددة

يُقدّم هذا النهج المُحسّن تحكماً أفضل وإدارة للموارد وقابلية للتطوير. تذكر أنه بالنسبة للمهام المرتبطة بوحدة المعالجة المركزية، فإن المعالجة المتعددة (باستخدام وحدة multiprocessing) تكون بشكل عام أكثر كفاءة في CPython من الخيوط نظرًا لقيود GIL. اختر النهج المناسب بناءً على ما إذا كانت مهامك مرتبطة بالمدخلات/المخرجات أو وحدة المعالجة المركزية.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *