[python] هل الأقفال غير ضرورية في شفرة بايثون متعددة الخيوط بسبب GIL؟



3 Answers

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

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

Question

إذا كنت تعتمد على تطبيق Python الذي يحتوي على Global Interpreter Lock (أي CPython) وكتابة رمز multithreaded ، هل تحتاج حقًا إلى تأمين على الإطلاق؟

إذا كان GIL لا يسمح بتنفيذ تعليمات متعددة بالتوازي ، ألن تكون البيانات المشتركة غير ضرورية للحماية؟

آسف إذا كان هذا سؤالًا غبيًا ، ولكنه شيء لطالما تساءلت بشأن بيثون في الأجهزة متعددة المعالجات / النواة.

نفس الشيء ينطبق على أي تطبيق لغة أخرى لديها GIL.




لا تزال هناك حاجة إلى أقفال. سأحاول شرح سبب الحاجة إليها.

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

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

#increment value
global var
read_var = var
var = read_var + 1

كما هو موضح أعلاه ، تضمن GIL فقط أن اثنين من سلاسل الرسائل لا يمكن تنفيذ تعليمة في وقت واحد ، مما يعني أنه لا يمكن تنفيذ read_var = var في أي لحظة زمنية معينة. ولكن يمكنهم تنفيذ التعليمات واحدة تلو الأخرى ولا يزال لديك مشكلة. خذ بعين الاعتبار هذا الموقف:

  • لنفترض أن read_var هو 0.
  • يتم عقد GIL بواسطة مؤشر ترابط t1.
  • t1 ينفذ read_var = var . لذا ، فإن read_var في t1 هي 0. سيضمن GIL فقط أنه لن يتم تنفيذ عملية القراءة هذه لأي مؤشر ترابط آخر في هذه اللحظة.
  • يتم إعطاء GIL إلى مؤشر ترابط t2.
  • t2 ينفذ read_var = var . ولكن read_var لا يزال 0. لذا ، read_var في t2 هو 0.
  • يتم إعطاء GIL إلى t1.
  • وينفذ t1 var = read_var+1 ويصبح var 1.
  • يتم إعطاء GIL إلى t2.
  • يعتقد t2 read_var = 0 ، لأن هذا هو ما يقرأ.
  • وينفذ t2 var = read_var+1 ويصبح var 1.
  • توقعنا هو أن var يجب أن يصبح 2.
  • لذلك ، يجب استخدام قفل للحفاظ على كل من القراءة وزيادة على حد سواء كعملية ذرية.
  • سوف يشرح الجواب هاريس ذلك من خلال مثال التعليمات البرمجية.



أعتقد أنه من هذا الطريق:

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

المشكلة هي أنه يمكن تعليق موضوع التنفيذ في أي مكان ، على سبيل المثال ، إذا كنت أرغب في حساب b = (a + b) * 3 ، فقد ينتج عن ذلك تعليمات مثل هذا:

1    a += b
2    a *= 3
3    b = a

الآن ، دعنا نفترض أن قيد التشغيل في سلسلة رسائل وأن سلسلة المحادثات قد تم تعليقها بعد السطر الأول أو الثاني ، ثم يتم تشغيل مؤشر ترابط آخر وتشغيلها:

b = 5

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

لذلك يمكنك أن ترى أنه على الرغم من أنها لا تعمل في الوقت نفسه ، إلا أنك لا تزال بحاجة إلى قفل.




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

الإجابة التي واجهتها مرارًا وتكرارًا هي أن تعدد الوسائط في بايثون نادراً ما يستحق قيمة الحمل ، بسبب هذا. لقد سمعت أشياء جيدة عن مشروع PyProcessing ، مما يجعل تشغيل عمليات متعددة "بسيطة" مثل multithreading ، مع هياكل البيانات المشتركة ، قوائم الانتظار ، وما إلى ذلك (سيتم إدخال PyProcessing في المكتبة القياسية ل Python 2.6 المرتقب كوحدة multiprocessing .) وهذا يجعلك في جميع أنحاء GIL ، حيث أن كل عملية لها مترجمها الخاص.




Related