Programming Basics SQL HTML CSS JavaScript Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Learn Typing

بايثونتعدد المهام

  • مفهوم تعدد المهام
  • طريقة إنشاء ثريد
  • مفهوم الثريد الأساسي
  • دوال الموديول threading
  • خصائص و دوال الكلاس Thread
  • أهمية مزامنة الثريدات
  • ما هي Deadlock؟
  • تجميع الثريدات بداخل Queue
  • الفرق بين Process و Thread

مفهوم تعدد المهام

عندما تستخدم هاتفك أو حاسوبك، ترى أنه يمكنك تشغيل عدة برامج مع بعض في وقت واحد، كل برنامج شغال في الذاكرة يعتبر Process، فمثلاً إذا قمت بتشغيل خمسة برامج مع بعض فهذا يعني أن نظام التشغيل ينظم عمل خمسة Processes مع بعض. آلية تشغيل عدة برامج مع بعض تسمى Multiprocessing.

من جهة أخرى، في البرنامج الواحد يمكنك تنفيذ عدة أوامر مع بعض و جعل المستخدم يشعر كأنها تتنفذ في وقت واحد، فمثلاً في حال كنت تلعب لعبة كلعبة كرة القدم، تجد أنه هناك عدة أشياء تحدث في وقت واحد حيث أنك تسمع عدة أصوات (مثل أغنية حماسية، صوت المعلق، صوت المشجعين، صوت صفارة الحكم في حال وقع خطأ إلخ..)، بالإضافة إلى أنه يمكنك تحريك اللاعب و مشاهدة توقيت المبارة و الكثير من التفاصيل الأخرى التي تحدث كلها في نفس الوقت لتصنع لك لعبة رائعة. هذه الآلية تسمى تعدد المهام ( Multithreading لأن كل جزء شغال في البرنامج يكون عبارة عن مجموعة أوامر موضوعة بداخل ثريد ( Thread ) خاص.

نستخدم آلية Multithreading لجعل البرنامج قادر على تنفيذ عدة أوامر مع بعض و كأنها تتنفذ في وقت واحد.


أهمية تعدد المهام

  • جعل المستخدم قادر على القيام بعدة عمليات مع بعض في نفس الوقت.
  • جعل تصميم التطبيقات أجمل و إضافة مؤثرات فيه.
  • أوامر الثريد تتنفذ بشكل منعزل عن باقي الثريدات الموجودة في البرنامج، و بالتالي فإنه في حال وقوع أي خطأ في ثريد فإنه لن يؤثر على أي ثريد آخر شغال في البرنامج.

طريقة إنشاء ثريد

إبتداءاً من الإصدار 2.4 في بايثون، أصبح الموديول threading هو الموديول الذي يستخدم لإنشاء ثريد.
هذا الموديول عبارة عن موديول جاهز في بايثون، و هو يحتوي على الكلاس Thread الذي يستخدم لبناء ثريد و التحكم به.

قبل هذا الموديول كان يوجد موديول إسمه _thread يستخدم لهذا الغرض أيضاً لكنه لم يعد يستخدم الآن و سيتم إلغاؤه مستقبلاً. و بالطبع ننصح بعدم استخدام الموديول _thread أو الإعتماد عليه.


خطوات بناء ثريد

  1. يجب تضمين الموديول threading.
  2. بناء كلاس يرث من الكلاس Thread و فيه نفعل Override لدالة إسمها run().
  3. في الدالة run() نضع الأوامر التي تريدها أن تتنفذ عندما يتم تشغيل الثريد.

خطوات تشغيل ثريد

  1. يجب إنشاء كائن من الكلاس الذي يرث من الكلاس Thread.
  2. من هذا الكائن، نقوم باستدعاء دالة جاهزة إسمها start().

عند استدعاء الدالة start() من الكائن الذي يمثل ثريد فإنها تقوم بشكل تلقائي باستدعاء الدالة run() منه و تنفيذ الأوامر الموضوعة فيها.


في المثال التالي قمنا بإنشاء كلاس إسمه Worker يرث من الكلاس Thread.

في الدالة __init__() الخاصة بهذا الكلاس، قمنا بتعريف خاصية إسمها name لأننا ننوي إعطاء إسم لكل كائن ننشئه من هذا الكلاس. كما أننا جعلناها تستدعي الدالة __init__() الموجودة في الكلاس Thread لنكون قادرين على معاملة أي كائن ننشئه من هذا الكلاس كثريد.

بعدها فعلنا Override للدالة run() بهدف أن تطبع الإسم الذي نضعه في الخاصية name ثلاث مرات عندما يتم تشغيله.

في النهاية، قمنا بإنشاء كائنين من هذا الكلاس و تشغيلهما في وقت واحد.

مثال

Worker.py
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة
# Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
class Worker(threading.Thread):
# Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
# name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
def __init__(self, name):
super(Worker, self).__init__()
self.name = name
# لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
def run(self):
# في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
print('Starting', self.name)
# sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
# و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
for i in range(3):
print(self.name)
time.sleep(1)
# في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
print('Ending', self.name)
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه class Worker(threading.Thread): # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية def __init__(self, name): super(Worker, self).__init__() self.name = name # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة def run(self): # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه print('Starting', self.name) # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة for i in range(3): print(self.name) time.sleep(1) # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه print('Ending', self.name)
Test.py
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
from Worker import Worker
# Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس
thread1 = Worker('Thread-1')
thread2 = Worker('Thread-2')
# مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة
thread1.start()
thread2.start()
# لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا قمنا باستدعاء الدالة
# قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين
thread1.join()
thread2.join()
# تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين
print('Both threads are end')
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس from Worker import Worker # Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس thread1 = Worker('Thread-1') thread2 = Worker('Thread-2') # مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة thread1.start() thread2.start() # لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا قمنا باستدعاء الدالة # قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين thread1.join() thread2.join() # تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين print('Both threads are end')

النتيجة: عند تشغيل الملف Test سنحصل على نتيجة مشابهة لما يلي.

Starting Thread-1
Thread-1
Starting Thread-2
Thread-2
Thread-2
Thread-1
Thread-2
Thread-1
Ending Thread-2
Ending Thread-1
Both threads are end

في المثال السابق، لم يكن هناك حاجة لتعريف خاصية إسمها name في الكلاس Worker لأنه في الواقع قد ورث بالفعل خاصية إسمها name من الكلاس Thread.
نستنتج أنه كان باستطاعتنا تمرير الإسم مباشرةً إلى الخاصية التي ورثها الكلاس Worker بدل تعريفها فيه من جديد.


هل يمكن تحديد أي ثريد سيتنفذ قبل الآخر؟

عند تشغيل أكثر من ثريد في وقت واحد، لا يمكن ضمان أو تحديد أي ثريد سيتنفذ أو ينتهي قبل الآخر.
السبب في ذلك أن معالج الحاسوب ( CPU ) سيقوم بإرسال كل ثريد تم تشغيله إلى نواة ( Core ) من الأنوية الموجودة في المعالج حتى ينفذهم بشكل متوازي و في ذات الوقت.

منطقياً، النواة التي عليها ضغط أقل ستنتهي من تنفيذ أوامر الثريد بشكل أسرع.
لهذا السبب، إذا قمت بتشغيل المثال السابق أكثر من مرة فستجد أنك في كل مرة ستحصل على نتيجة مختلفة.

مفهوم الثريد الأساسي

في بايثون، كل كود يتنفذ في البرنامج، فإنه حتماً يتنفذ بداخل ثريد واحد على الأقل.
أي حتى لو لم تقم بوضع الكود بداخل ثريد فإنه سيتم وضعه في ثريد يقال له الثريد الأساسي ( Main Thread ).

في حال تم تشغيل ثريد في البرنامج فهذا يعني أن البرنامج حالياً يعمل فيه إثنين ثريد و هما الثريد الأساسي بالإضافة إلى الثريد الذي تم تشغيله.


في المثال التالي قمنا بطباعة عدد الثريدات التي تتنفذ حالياً علماً بأننا لم نقم بإنشاء أي كائن من كلاس يرث من الكلاس Thread.

مثال

Worker.py
# حتى نستطيع استخدام الدوال الموجودة فيه threading هنا قمنا بتضمين الموديول
import threading
# الذين يعملون حالياً Threads هنا قمنا بطباعة عدد الـ
print('Active thread(s) count:', threading.active_count())
# تم تشغيله Thread الأساسي في البرنامج. أي أول Thread هنا قمنا بطباعة إسم الـ
print('Main thread object:', threading.main_thread().name)
# الحالي الذي يتم تنفيذه Thread هنا قمنا بطباعة إسم الـ
print('Current thread object:', threading.main_thread().name)
# حتى نستطيع استخدام الدوال الموجودة فيه threading هنا قمنا بتضمين الموديول import threading # الذين يعملون حالياً Threads هنا قمنا بطباعة عدد الـ print('Active thread(s) count:', threading.active_count()) # تم تشغيله Thread الأساسي في البرنامج. أي أول Thread هنا قمنا بطباعة إسم الـ print('Main thread object:', threading.main_thread().name) # الحالي الذي يتم تنفيذه Thread هنا قمنا بطباعة إسم الـ print('Current thread object:', threading.main_thread().name)

النتيجة: سنحصل على نتيجة مشابهة لما يلي.

Active thread(s) count: 1
Main thread object: MainThread
Current thread object: MainThread

نلاحظ أن عدد الثريدات التي تتنفذ حالياً هو 1، مما يعني أن الكود الأساسي في البرنامج يتم وضعه في ثريد.
بالإضافة إلى ذلك فإن إسم الثريد الأساسي الذي يتنفذ حالياً هو MainThread.

دوال الموديول threading

الجدول التالي يحتوي على دوال الموديول threading الأكثر إستخداماً و التي سبق أن استخدمناها في الأمثلة السابقة.

إسم الدالة مع تعريفها
1 threading.active_count() ترجع عدد الثريدات التي تتنفذ في الوقت الحالي الذي تم فيه إستدعاءها.
2 threading.main_thread() ترجع كائن الـ thread الأساسي في البرنامج.
معلومة: الثريد الأساسي في البرنامج هو أول ثريد يبدأ مفسر لغة بايثون بتنفيذ الأوامر الموضوعة فيه.
3 threading.current_thread() ترجع كائن الـ thread الذي يتنفذ في الوقت الحالي الذي تم فيه إستدعاءها.
4 threading.enumerate() ترجع كائن list يحتوي على كل كائن thread يتنفذ في الوقت الحالي الذي تم فيه إستدعاءها.
ملاحظة: لا ترجع أي ثريد في حال تم إيقافه أو لم يتم تشغيله من الأساس.

خصائص و دوال الكلاس Thread

الجدول التالي يحتوي على دوال الكلاس Thread الأكثر إستخداماً و التي سبق أن استخدمناها في الأمثلة السابقة.

إسم الدالة مع تعريفها
1 run() نفعل لها Override لنضع فيها الأوامر التي نريدها أن تتنفذ عند تشغيل كائن الـ Thread.
2 start() تستخدم لتشغيل كائن الـ Thread الذي قام باستدعائها.
فعلياً، الدالة start() تقوم فقط باستدعاء الدالة run() لتنفيذ الأوامر الموضوعة فيها.
إنتبه: لا يمكن تشغيل نفس كائن الـ Thread أكثر من مرة. أي لا يمكن إستدعاء الدالة start() أكثر من مرة من نفس كائن الـ Thread. و في حال تم استدعاء الدالة start() مرتين من نفس كائن الـ Thread فإنها ترمي الإستثناء RuntimeError.
3 is_alive() ترجع True طالما أن كائن الـ Thread لم ينتهي تنفيذه بعد و حتى لو كان لم يتم البدء بتنفيذه أصلاً.
غير ذلك ترجع False.
4 join(timeout=None) تجعل كائن الـ Thread الذي قام باستدعائها ينتظر انتهاء تنفيذ أي كائن Thread يعمل قبله قبل أن يبدأ هو بتنفيذ الأوامر الموجودة فيه.
timeout هو باراميتر إختياري مكانه يمكن تمرير عدد عشري يمثل عدد الثواني التي سينتظرها كائن الـ Thread عند يحين دوره حتى يشتغل. فمثلاً إذا قمت بتمرير الرقم 1.5 فهذا يعني أنك تقصد ثانية و نصف.

معلومة: هذه الدالة مهمة عند الحاجة لجعل الثريدات تتنفذ الواحد تلو الآخر بدلاً من أن تتنفذ في وقت واحد في حال كان من الممكن أن تسبب مشكلة تسمى Deadlock. مع العلم أنه في حال وجد مترجم لغة بايثون أن تشغيل الثريد يمكن أن يسبب Deadlock فإنه سيتم رمي الإستثناء RuntimeError.

الجدول التالي يحتوي على خصائص الكلاس Thread.

إسم الخاصية مع تعريفها
1 name ترجع الإسم الذي تم إعطاؤه لكائن الـ Thread الذي قام باستدعائها.
فعلياً، عند إنشاء كائن من الكلاس الذي يرث من الكلاس Thread يمكنك تمرير الإسم الذي تريد إعطاؤه له لحظة إنشاءه.
2 ident ترجع رقم التعرفة ( ID ) الذي يتم توليده بشكل عشوائي و إعطائه لكائن الـ Thread الذي قام باستدعائها.
3 daemon هذه الخاصية يمكن الإستفادة منها في حال كنت تنوي تشغيل ثريد من داخل ثريد آخر.
إذا مررت لها القيمة True قبل أن تقوم بتشغيل الثريد الآخر، سيفهم مترجم لغة بايثون أنك تنوي إيقاف الثريد الآخر بشكل تلقائي عندما يتوقف الثريد الذي قام باستدعائه في الأساس. كما أنه في حال كان الثريد الآخر متصل بملف ما، بقاعدة بيانات، أو بالإنترنت إلخ.. فإن إغلاقه بشكل مفاجئ لا يضمن أن يتم إغلاق الإتصالات التي كان يجريها.
في حال وجد مترجم لغة بايثون أن تشغيل الثريد يمكن أن يسبب Deadlock فإنه سيتم رمي الإستثناء RuntimeError.

أهمية مزامنة الثريدات

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


السيناريو الأول

في حال قمت ببناء ثريد مهمته جلب علامات الطالب المخزنة في قاعدة بيانات و من ثم حساب معدله العام، و بعدما تم حساب المعدل و عرضه للطالب، قام ثريد آخر بتعديل بعض العلامات في قاعدة البيانات لأنه وجد أن الطالب عنده غياب كثير.

في هذه الحالة النتيجة التي أعطانا إياها الثريد الأول ليست صحيحة، حيث أنه كان يفترض أن يتم حساب المعدل بعد أن تم إدخال أيام الغياب ضمن المعادلة التي تحسب له معدله النهائي و تعرض له إن كان ناجحاً بناءاً على معدله النهائي و عدد الأيام التي حضر فيها إلى الجامعة.

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


السيناريو الثاني

في حال كان يوجد ثريد يريد تعديل محتوى ملف، و كان يوجد ثريد آخر يقوم بقراءة محتوى نفس الملف فإن هذا الأمر قد يسبب مشكلتين:

  • الثريد الذي يقوم بالقراءة من الملف سيقرأ محتوى الملف القديم بدون معرفة أنه كان يتم تحديث محتواه في الوقت الذي كان يقرأ هو منه.
  • الثريد الذي يحاول التعديل في محتوى الملف قد لا يتمكن من حفظ التعديلات التي قام بإجرائها إن كان هناك ثريد آخر متصل به و يحاول قراءة محتواه.

لحل المشكلة السابقة، كان يجب مزامنة عمل الثريد الذي يقرأ من الملف و الثريد الذي يعدل في محتواه لضمان أن لا يتعاملا معه في وقت واحد.

ما هي Deadlock؟

في حال قمت بتشغيل إثنين ثريد مع بعض، و في مرحلة ما أصبح الإثنين عالقين بسبب أن الثريد الأول بحاجة للوصول إلى شيء يستخدمه الثريد الثاني. و بنفس الوقت الثريد الثاني بحاجة للوصول إلى شيء يستخدمه الثريد الأول. هذه المعضلة تسمى Deadlock, و يمكنك تخيلها كما في الصورة التالية.

مفهوم Deadlock

حل هذه المشكلة يكون بجعل الثريدات لا تشتغل في ذات الوقت، بل تعمل بشكل متتالي بحيث عندما ينتهي الأول من التنفيذ يبدأ الثاني بالعمل.


في المثال التالي قمنا بجعل الثريدات التي ننشئها من الكلاس Worker تعمل بطريقة متزامنة، أي الواحد تلو الآخر و ليس مع بعض.
ما فعلناه ببساطة، هو إنشاء كائن من الكلاس Lock، ثم وضع الكود الذي نريده أن يتنفذ بشكل متزامن بداخل بلوك من هذا الكائن.
ملاحظة: هذا نفسه المثال السابق مع بعض التعديلات، و قد قمنا بتعليم الأسطر التي قمنا بإضافتها عليه.

مثال

Worker.py
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة
# لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس
lock = threading.Lock()
# Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
class Worker(threading.Thread):
# Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
# name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
def __init__(self, name):
super(Worker, self).__init__()
self.name = name
# لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
def run(self):
# هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة
with lock:
# في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
print('Starting', self.name)
# sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
# و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
for i in range(3):
print(self.name)
time.sleep(1)
# في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
print('Ending', self.name)
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة # لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس lock = threading.Lock() # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه class Worker(threading.Thread): # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية def __init__(self, name): super(Worker, self).__init__() self.name = name # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة def run(self): # هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة with lock: # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه print('Starting', self.name) # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة for i in range(3): print(self.name) time.sleep(1) # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه print('Ending', self.name)
Test.py
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
from Worker import Worker
# Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس
thread1 = Worker('Thread-1')
thread2 = Worker('Thread-2')
# مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة
thread1.start()
thread2.start()
# لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا قمنا باستدعاء الدالة
# قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين
thread1.join()
thread2.join()
# تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين
print('Both threads are end')
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس from Worker import Worker # Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس thread1 = Worker('Thread-1') thread2 = Worker('Thread-2') # مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة thread1.start() thread2.start() # لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا قمنا باستدعاء الدالة # قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين thread1.join() thread2.join() # تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين print('Both threads are end')

نتيجة تشغيل الملف Test.py.

Starting Thread-1     <-- هنا تم البدء بتنفيذ الثريد الأول
Thread-1
Thread-1
Thread-1
Ending Thread-1       <-- هنا إنتهى تنفيذ أوامر الثريد الأول
Starting Thread-2     <-- هنا تم البدء بتنفيذ الثريد الثاني
Thread-2
Thread-2
Thread-2
Ending Thread-2       <-- هنا تم البدء بتنفيذ الثريد الثاني
Both threads are end

في حال أردت تطبيق أسلوب المزامنة بدون إستخدام أسلوب with block:، يجب وضع الكود الذي تريد مزامنته بين الدالتين acquire() و release(). إذاً يمكنك كتابة كود الدالة run() كالتالي و الحصول على نفس النتيجة.

def run(self):
block.acquire() # هنا قمنا بجعل الكود الذي سيتم تنفيذه بعدها يعمل بطريقة متزامنة
print('Starting', self.name)
for i in range(3):
print(self.name)
time.sleep(1)
print('Ending', self.name)
block.release() # هنا قمنا بإيقاف التزامن
def run(self): block.acquire() # هنا قمنا بجعل الكود الذي سيتم تنفيذه بعدها يعمل بطريقة متزامنة print('Starting', self.name) for i in range(3): print(self.name) time.sleep(1) print('Ending', self.name) block.release() # هنا قمنا بإيقاف التزامن

تجميع الثريدات بداخل Queue

في حال أردت تشغيل مجموعة ثريدات بشكل متزامن، يمكنك إنشاء كائن من الكلاس Queue و وضعهم فيه.
بعدها تقوم بإنشاء حلقة للمرور على ثريد واحد منهم في كل مرة، و من ثم تقوم بتشغيله.

الجدول التالي يحتوي على دوال الكلاس Queue الأكثر إستخداماً.

إسم الدالة مع تعريفها
1 get() ترجع العنصر التالي من كائن الـ Queue الذي قام باستدعائها و من ثم تقوم بحذفه منها.
في حالتنا سترجع ثريد جديد في كل مرة نستدعيها فيها.
في حال كان كائن الـ Queue فارغاً فإنها ترمي الإستثناء Empty.
2 put(item) تضيف الكائن الذي نمرره لها كعنصر في كائن الـ Queue الذي قام باستدعائها.
في حالتنا نمرر لها كائن من كلاس يمثل ثريد.
3 qsize() ترجع عدد العناصر الموجودة في كائن الـ Queue الذي قام باستدعائها.
في حالتنا ترجع عدد الثريدات الموجودين فيه و لم يتم تنفيذهم بعد.
4 empty() تستخدم لمعرفة ما إن كان كائن الـ Queue الذي قام باستدعائها فارغاً أم لا.
ترجع True إذا كان فارغاً، غير ذلك ترجع False.
5 Full() تستخدم لمعرفة ما إن كان كائن الـ Queue الذي قام باستدعائها ممتلىء أم لا، أي قادر على تخزين عناصر جديدة أم لا.
ترجع True إذا كان عدد العناصر الموضوعة فيه أقل من عدد العناصر التي يمكنه تخزينها، غير ذلك ترجع False.

في المثال التالي قمنا قمنا بإنشاء ثلاث كائنات تمثل ثريد و وضعناهم بشكل مؤقت في list إسمه listWorkers.
بعدها قمنا بتخزين كائنات الـ listWorkers في Queue إسمه queueWorkers.
في النهاية، قمنا باستخدام حلقة تمر على كل ثريد موجود في الكائن queueWorkers و تشغله.

ملاحظة: جعلنا الثريدات تعمل بشكل متزامن.
في حال لم ترد جعلها تعمل بشكل متزامن أزل الجملة with lock: الموضوعة في الدالة run().

مثال

Worker.py
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة
# لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس
lock = threading.Lock()
# Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
class Worker(threading.Thread):
# Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
# name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
def __init__(self, name):
super(Worker, self).__init__()
self.name = name
# لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
def run(self):
# هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة
with lock:
# في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
print('Starting', self.name)
# sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
# و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
for i in range(3):
print(self.name)
time.sleep(1)
# في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
print('Ending', self.name)
import threading # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس import time # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة # لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس lock = threading.Lock() # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه class Worker(threading.Thread): # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية def __init__(self, name): super(Worker, self).__init__() self.name = name # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة def run(self): # هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة with lock: # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه print('Starting', self.name) # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة for i in range(3): print(self.name) time.sleep(1) # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه print('Ending', self.name)
Test.py
from Worker import Worker # Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
from queue import Queue # queue الموجود في الموديول Queue هنا قمنا بتضمين الكلاس
# Thread أي وضعنا فيه 3 كائنات تمثل .Worker وضعنا فيه 3 كائنات من الكلاس هنا قمنا بإنشاء
worker= [
Worker('Thread-1'),
Worker('Thread-2'),
Worker('Thread-3')
]
# لأننا نريد وضع نفس العناصر بداخله workerعدد عناصره يساوي عدد عناصر الكائن Queue هنا قمنا بإنشاء كائن
workerQueue = Queue(len(workerList))
# workerQueue و من ثم تضيفه كعنصر في الكائن worker في كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة
# workerQueue في الكائن worker في النهاية سيتم وضع كل الكائنات الموجودة في الكائن
for thread in workerList:
workerQueue.put(thread)
# غير فارغ workerQueue لا تتوقف عن تكرار ما بداخلها طالما أن الكائن while هنا قمنا بإنشاء حلقة
# و من ثم تنفيذه workerQueue من الكائن Worker في كل دورة من دورات الحلقة سيتم إخراج كائن
while not workerQueue.empty():
workerQueue.get().start()
# workerفي كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة
# لكي يجعل مفسّر لغة بايثون لا ينفذ أي أوامر أخرى موجودة بعد الحلقة join() ترجعه سيستدعي الدالة Worker كل كائن
for thread in workerList:
thread.join()
# تماماً، سيتم تنفيذ أمر الطباعة التالي thread3 و thread2 ,thread1 بعد أن تتوقف الكائنات
print('All threads are end')
from Worker import Worker # Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس from queue import Queue # queue الموجود في الموديول Queue هنا قمنا بتضمين الكلاس # Thread أي وضعنا فيه 3 كائنات تمثل .Worker وضعنا فيه 3 كائنات من الكلاس هنا قمنا بإنشاء worker= [ Worker('Thread-1'), Worker('Thread-2'), Worker('Thread-3') ] # لأننا نريد وضع نفس العناصر بداخله workerعدد عناصره يساوي عدد عناصر الكائن Queue هنا قمنا بإنشاء كائن workerQueue = Queue(len(workerList)) # workerQueue و من ثم تضيفه كعنصر في الكائن worker في كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة # workerQueue في الكائن worker في النهاية سيتم وضع كل الكائنات الموجودة في الكائن for thread in workerList: workerQueue.put(thread) # غير فارغ workerQueue لا تتوقف عن تكرار ما بداخلها طالما أن الكائن while هنا قمنا بإنشاء حلقة # و من ثم تنفيذه workerQueue من الكائن Worker في كل دورة من دورات الحلقة سيتم إخراج كائن while not workerQueue.empty(): workerQueue.get().start() # workerفي كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة # لكي يجعل مفسّر لغة بايثون لا ينفذ أي أوامر أخرى موجودة بعد الحلقة join() ترجعه سيستدعي الدالة Worker كل كائن for thread in workerList: thread.join() # تماماً، سيتم تنفيذ أمر الطباعة التالي thread3 و thread2 ,thread1 بعد أن تتوقف الكائنات print('All threads are end')

نتيجة تشغيل الملف Test.

Starting Thread-1     <-- هنا تم البدء بتنفيذ الثريد الأول
Thread-1
Thread-1
Thread-1
Ending Thread-1       <-- هنا إنتهى تنفيذ أوامر الثريد الأول
Starting Thread-2     <-- هنا تم البدء بتنفيذ الثريد الثاني
Thread-2
Thread-2
Thread-2
Ending Thread-2       <-- هنا إنتهى تنفيذ أوامر الثريد الثاني
Starting Thread-3     <-- هنا تم البدء بتنفيذ الثريد الثالث
Thread-3
Thread-3
Thread-3
Ending Thread-3       <-- هنا إنتهى تنفيذ أوامر الثريد الثالث
All threads are end

الفرق بين Process و Thread

بروسيس ( Process ) تعني برنامج شغال حالياً، و قد قام نظام التشغيل بحجز مساحة خاصة له في الذاكرة.

ثريد ( Thread ) عبارة عن مجموعة أوامر يتم تنفيذها أثناء تنفيذ أوامر أخرى في نفس البرنامج. يمكنك تشغيل أكثر من ثريد في نفس الوقت في البرنامج، و يمكن أيضاً مشاركة المعلومات بينهم. مع ملاحظة أنه يتم إنشاء جميع الثريدات من ضمن المساحة المحجوزة للبروسيس في الذاكرة.

الفرق بين Process و Thread