تعدد الخيوط في Python مع مثال: تعلم GIL في Python

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

ما هو الموضوع؟

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

ما هي العملية؟

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

ما هو تعدد في Python?

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

ما هي المعالجة المتعددة؟

المعالجة المتعددة يتيح لك تشغيل عمليات متعددة غير مرتبطة في نفس الوقت. لا تتشارك هذه العمليات مواردها وتتواصل من خلال IPC.

Python تعدد العمليات مقابل المعالجة المتعددة

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

سيكون لكل عملية عنصرين أساسيين:

  • المدونة
  • البيانات

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

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

لماذا استخدام تعدد المواضيع؟

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

Python خيوط متعددة

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

  1. استخدم خيط الوحدة النمطية، و
  2. استخدم خيوط وحدة

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

وحدات الخيط والخيوط

الوحدتان اللتان ستتعرف عليهما في هذا البرنامج التعليمي هما وحدة الموضوع و مبادئ السلوك وحدة خيوط.

ومع ذلك، فقد تم إهمال وحدة مؤشر الترابط منذ فترة طويلة. بدءا من Python 3، تم تصنيفه على أنه قديم ولا يمكن الوصول إليه إلا كـ __خيط للتوافق مع الإصدارات السابقة.

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

وحدة الموضوع

بناء الجملة لإنشاء موضوع جديد باستخدام هذه الوحدة هو كما يلي:

thread.start_new_thread(function_name, arguments)

حسنًا، لقد قمت الآن بتغطية النظرية الأساسية لبدء البرمجة. لذلك، افتح الخاص بك IDLE أو مفكرة واكتب فيها ما يلي:

import time
import _thread

def thread_test(name, wait):
   i = 0
   while i <= 3:
      time.sleep(wait)
      print("Running %s\n" %name)
      i = i + 1

   print("%s has finished execution" %name)

if __name__ == "__main__":
    
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

احفظ الملف واضغط على F5 لتشغيل البرنامج. إذا تم كل شيء بشكل صحيح، فهذه هي النتيجة التي يجب أن تراها:

وحدة الموضوع

سوف تتعلم المزيد عن ظروف السباق وكيفية التعامل معها في الأقسام القادمة

وحدة الموضوع

شرح الكود

  1. تستورد هذه العبارات وحدة الوقت والخيط المستخدمة للتعامل مع التنفيذ والتأخير Python الخيوط.
  2. هنا قمت بتعريف دالة تسمى موضوع_اختبار, والذي سوف يطلق عليه start_new_thread طريقة. تقوم الدالة بتشغيل حلقة while لأربعة تكرارات وتطبع اسم الخيط الذي أطلق عليها. بمجرد اكتمال التكرار، فإنه يطبع رسالة تفيد بأن مؤشر الترابط قد انتهى من التنفيذ.
  3. هذا هو القسم الرئيسي لبرنامجك. هنا، يمكنك ببساطة الاتصال بـ start_new_thread الطريقة مع thread_test تعمل كوسيطة. سيؤدي هذا إلى إنشاء مؤشر ترابط جديد للوظيفة التي تمررها كوسيطة والبدء في تنفيذها. لاحظ أنه يمكنك استبدال هذا (thread_test) مع أي وظيفة أخرى تريد تشغيلها كسلسلة رسائل.

وحدة الخيوط

هذه الوحدة هي التنفيذ عالي المستوى للترابط في بايثون والمعيار الفعلي لإدارة التطبيقات متعددة الخيوط. يوفر مجموعة واسعة من الميزات عند مقارنتها بوحدة الخيط.

هيكل وحدة الخيوط
هيكل وحدة الخيوط

فيما يلي قائمة ببعض الوظائف المفيدة المحددة في هذه الوحدة:

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

الخلفية الدرامية: فئة الموضوع

قبل أن تبدأ في برمجة برامج متعددة الخيوط باستخدام وحدة الخيط، من المهم أن تفهم فئة الخيط. فئة الخيط هي الفئة الأساسية التي تحدد القالب وعمليات الخيط في بايثون.

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

تشير فئة Thread، باختصار، إلى تسلسل تعليمات برمجية يتم تشغيله بشكل منفصل خيط السيطرة.

لذا، عند كتابة تطبيق متعدد الخيوط، ستقوم بما يلي:

  1. تحديد فئة تمتد فئة الموضوع
  2. تجاوز __init__ منشئ
  3. تجاوز يركض() طريقة

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

الآن، دعونا نحاول استخدام وحدة الترابط لتنفيذ المثال السابق. مرة أخرى، أطلق النار على الخاص بك IDLE واكتب ما يلي:

import time
import threading

class threadtester (threading.Thread):
    def __init__(self, id, name, i):
       threading.Thread.__init__(self)
       self.id = id
       self.name = name
       self.i = i
       
    def run(self):
       thread_test(self.name, self.i, 5)
       print ("%s has finished execution " %self.name)

def thread_test(name, wait, i):

    while i:
       time.sleep(wait)
       print ("Running %s \n" %name)
       i = i - 1

if __name__=="__main__":
    thread1 = threadtester(1, "First Thread", 1)
    thread2 = threadtester(2, "Second Thread", 2)
    thread3 = threadtester(3, "Third Thread", 3)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

سيكون هذا هو الإخراج عند تنفيذ التعليمات البرمجية أعلاه:

الخلفية الدرامية: فئة الموضوع

شرح الكود

الخلفية الدرامية: فئة الموضوع

  1. هذا الجزء هو نفس المثال السابق. هنا، يمكنك استيراد وحدة الوقت والخيط المستخدمة للتعامل مع تنفيذ وتأخيرات Python الخيوط.
  2. في هذا الجزء، تقوم بإنشاء فئة تسمى Threadtester، والتي ترث أو توسع نطاق خيط فئة وحدة الخيوط. هذه إحدى الطرق الأكثر شيوعًا لإنشاء سلاسل رسائل في بايثون. ومع ذلك، يجب عليك فقط تجاوز المنشئ و يركض() الطريقة في تطبيقك كما ترون في نموذج التعليمات البرمجية أعلاه، فإن __init__ تم تجاوز الطريقة (المنشئ). وبالمثل، لقد تجاوزت أيضًا يركض() طريقة. يحتوي على الكود الذي تريد تنفيذه داخل سلسلة رسائل. في هذا المثال، قمت باستدعاء الدالة thread_test().
  3. هذه هي طريقة thread_test () التي تأخذ قيمة i كوسيطة، يقللها بمقدار 1 عند كل تكرار ويتكرر خلال بقية الكود حتى يصبح i 0. في كل تكرار، يطبع اسم مؤشر الترابط الذي يتم تنفيذه حاليًا وينام لمدة ثوانٍ الانتظار (والتي يتم أخذها أيضًا كوسيطة ).
  4. thread1 = threadtester(1, “First Thread”، 1) هنا، نقوم بإنشاء موضوع وتمرير المعلمات الثلاثة التي أعلنا عنها في __init__. المعلمة الأولى هي معرف الخيط، والمعلمة الثانية هي اسم الخيط، والمعلمة الثالثة هي العداد، الذي يحدد عدد المرات التي يجب أن يتم فيها تشغيل الحلقة.
  5. Thread2.start()T يتم استخدام طريقة البدء لبدء تنفيذ الموضوع. داخليًا، تستدعي الدالة start() طريقة التشغيل () لفصلك.
  6. thread3.join() تمنع طريقة join() تنفيذ تعليمات برمجية أخرى وتنتظر حتى ينتهي الخيط الذي تم استدعاؤه عليه.

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

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

الجمود وظروف السباق

قبل التعرف على حالات الجمود والسباق، سيكون من المفيد فهم بعض التعريفات الأساسية المتعلقة بالبرمجة المتزامنة:

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

جمود

جمود تعد مشكلة الجمود من أكثر المشكلات التي يخشاها المطورون عند كتابة تطبيقات متزامنة/متعددة الخيوط في بايثون. أفضل طريقة لفهم الجمود هي استخدام مشكلة مثال علوم الكمبيوتر الكلاسيكية المعروفة باسم السفره Philoمشكلة الصوفيين.

المشكلة التي يطرحها فلاسفة الطعام هي كما يلي:

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

السفره Philoمشكلة الصوفيين

السفره Philoمشكلة الصوفيين

في أي وقت من الأوقات، يجب على الفيلسوف أن يأكل أو يفكر.

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

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

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

شروط السباق

حالة السباق هي حالة غير مرغوب فيها للبرنامج تحدث عندما يقوم النظام بتنفيذ عمليتين أو أكثر في نفس الوقت. على سبيل المثال، ضع في اعتبارك حلقة for البسيطة هذه:

i=0; # a global variable
for x in range(100):
    print(i)
    i+=1;

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

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

Syncكرونة المواضيع

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

القفل هو بدائية مزامنة منخفضة المستوى يتم تنفيذها بواسطة __خيط وحدة. في أي وقت، يمكن أن يكون القفل في إحدى الحالتين: مقفل or مفتوحة. وهو يدعم طريقتين:

  1. يستحوذ على()عندما يتم إلغاء قفل حالة القفل، فإن استدعاء طريقة الاكتساب () سيؤدي إلى تغيير الحالة إلى القفل والعودة. ومع ذلك، إذا كانت الحالة مقفلة، فسيتم حظر استدعاء الحصول على () حتى يتم استدعاء طريقة الإصدار () بواسطة مؤشر ترابط آخر.
  2. إفراج()يتم استخدام طريقة الإصدار () لتعيين الحالة على إلغاء القفل، أي لتحرير القفل. يمكن استدعاؤه بواسطة أي خيط، وليس بالضرورة الخيط الذي حصل على القفل.

فيما يلي مثال على استخدام الأقفال في تطبيقاتك. أطلق النار على الخاص بك IDLE واكتب ما يلي:

import threading
lock = threading.Lock()

def first_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the first funcion')
        lock.release()

def second_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the second funcion')
        lock.release()

if __name__=="__main__":
    thread_one = threading.Thread(target=first_function)
    thread_two = threading.Thread(target=second_function)

    thread_one.start()
    thread_two.start()

    thread_one.join()
    thread_two.join()

الآن، اضغط على F5. يجب أن تشاهد إخراجًا مثل هذا:

Syncكرونة المواضيع

شرح الكود

Syncكرونة المواضيع

  1. هنا، أنت ببساطة تقوم بإنشاء قفل جديد عن طريق الاتصال بـ خيوط. قفل () وظيفة المصنع . داخليًا، تقوم Lock() بإرجاع مثيل لفئة القفل الملموسة الأكثر فعالية والتي يتم الحفاظ عليها بواسطة النظام الأساسي.
  2. في العبارة الأولى، يمكنك الحصول على القفل عن طريق استدعاء طريقة الاستحواذ (). عندما يتم منح القفل، يمكنك الطباعة "تم الحصول على القفل" إلى وحدة التحكم. بمجرد الانتهاء من تنفيذ جميع التعليمات البرمجية التي تريد تشغيل مؤشر الترابط، يمكنك تحرير القفل عن طريق استدعاء طريقة الإصدار ().

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

بصرف النظر عن الأقفال، يدعم بايثون أيضًا بعض الآليات الأخرى للتعامل مع مزامنة الخيوط كما هو موضح أدناه:

  1. رلوكس
  2. Semaphores
  3. الشروط
  4. الأحداث و
  5. حواجز

قفل المترجم العالمي (وكيفية التعامل معه)

قبل الدخول في تفاصيل GIL الخاصة بـ Python، دعنا نحدد بعض المصطلحات التي ستكون مفيدة في فهم القسم القادم:

  1. التعليمات البرمجية المرتبطة بوحدة المعالجة المركزية (CPU): يشير هذا إلى أي جزء من التعليمات البرمجية التي سيتم تنفيذها مباشرة بواسطة وحدة المعالجة المركزية (CPU).
  2. رمز الإدخال/الإخراج: يمكن أن يكون هذا أي رمز يصل إلى نظام الملفات من خلال نظام التشغيل
  3. CPython: هو المرجع التنفيذ of Python ويمكن وصفه بأنه مترجم مكتوب بلغة C و Python (لغة ​​البرمجة).

ما هو جيل في Python?

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

يمكن استخدام القفل للتأكد من أن مؤشر ترابط واحد فقط لديه حق الوصول إلى مورد معين في وقت معين.

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

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

  1. يقوم مترجم بايثون بإنشاء عملية جديدة وينتج الخيوط
  2. عندما يبدأ تشغيل مؤشر الترابط 1، سيحصل أولاً على GIL ويقفله.
  3. إذا أراد مؤشر الترابط 2 التنفيذ الآن، فسيتعين عليه الانتظار حتى يتم إصدار GIL حتى لو كان هناك معالج آخر مجاني.
  4. الآن، لنفترض أن الخيط 1 ينتظر عملية إدخال/إخراج. في هذا الوقت، سيحرر GIL، وسيقوم الخيط 2 بالحصول عليه.
  5. بعد إكمال عمليات الإدخال/الإخراج، إذا أراد مؤشر الترابط 1 التنفيذ الآن، فسيتعين عليه الانتظار مرة أخرى حتى يتم إصدار GIL بواسطة مؤشر الترابط 2.

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

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

لماذا كانت هناك حاجة لجيل؟

وCPython يستخدم جامع القمامة تقنية فعالة لإدارة الذاكرة تُعرف باسم عد المراجع. إليك كيفية عملها: كل كائن في بايثون لديه عدد مراجع، والذي يزداد عندما يتم تعيينه إلى اسم متغير جديد أو إضافته إلى حاوية (مثل الثنائيات والقوائم وما إلى ذلك). وبالمثل، يتم تقليل عدد المراجع عندما يخرج المرجع عن النطاق أو عند استدعاء عبارة del. عندما يصل عدد المراجع لكائن إلى 0، يتم جمعه في سلة المهملات، ويتم تحرير الذاكرة المخصصة.

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

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

ملخص

  • Python يدعم وحدتين لتعدد الخيوط:
    1. __خيط الوحدة النمطية: توفر تطبيقًا منخفض المستوى للترابط وقد عفا عليه الزمن.
    2. وحدة خيوط: يوفر تطبيقًا عالي المستوى لمؤشرات الترابط المتعددة وهو المعيار الحالي.
  • لإنشاء سلسلة باستخدام وحدة الترابط، يجب عليك القيام بما يلي:
    1. إنشاء فئة تمتد خيط فئة.
    2. تجاوز منشئه (__init__).
    3. تجاوز لها يركض() الأسلوب.
    4. إنشاء كائن من هذه الفئة.
  • يمكن تنفيذ الخيط عن طريق استدعاء بداية() الأسلوب.
  • استخدم انضم() يمكن استخدام الطريقة لحظر سلاسل الرسائل الأخرى حتى ينتهي تنفيذ هذا الخيط (الخيط الذي تم استدعاء الانضمام عليه).
  • تحدث حالة السباق عند وصول عدة سلاسل رسائل إلى مورد مشترك أو تعديله في نفس الوقت.
  • يمكن تجنبه عن طريق Syncكرونة المواضيع.
  • Python يدعم 6 طرق لمزامنة الخيوط:
    1. أقفال
    2. رلوكس
    3. Semaphores
    4. الشروط
    5. الأحداث و
    6. حواجز
  • تسمح الأقفال فقط لخيط معين حصل على القفل بالدخول إلى القسم الحرج.
  • يحتوي القفل على طريقتين أساسيتين:
    1. يستحوذ على(): يقوم بتعيين حالة القفل على مقفل. إذا تم استدعاؤها على كائن مقفل، فسيتم حظره حتى يصبح المورد مجانيًا.
    2. إفراج(): يقوم بتعيين حالة القفل على مفتوح ويعود. إذا تم استدعاؤها على كائن غير مقفل، فإنها ترجع كاذبة.
  • قفل المترجم العالمي عبارة عن آلية يتم من خلالها تحويل 1 C فقطPython يمكن تنفيذ عملية المترجم في وقت واحد.
  • تم استخدامه لتسهيل وظيفة العد المرجعي لـ CPythonجامع القمامة.
  • لجعل Python بالنسبة للتطبيقات التي تتطلب عمليات كثيفة تعتمد على وحدة المعالجة المركزية، فيجب عليك استخدام وحدة المعالجة المتعددة.

تلخيص هذه التدوينة بـ: