سيركز هذا البرنامج التعليمي على أحد موضوعات بايثون المهمة، وهو GIL. سنغطي أيضًا كيفية تأثير GIL على أداء برامج Python من خلال تنفيذ التعليمات البرمجية. قبل الغوص في هذا الموضوع، دعونا نحصل على فكرة أساسية عن GIL.
GIL أو قفل المترجم العالمي
يعد Python Global Interpreter Lock أو GIL جزءًا مهمًا من برمجة تعدد مؤشرات الترابط. إنه نوع من قفل العملية يستخدم عند العمل مع عمليات متعددة. أنه يعطي السيطرة على موضوع واحد فقط. بشكل عام، تستخدم بايثون خيطًا واحدًا لتشغيل عملية واحدة. نحصل على نفس نتيجة الأداء للعمليات ذات الخيوط المفردة والمتعددة الخيوط باستخدام GIL. إنه يقيد تحقيق تعدد الخيوط في بايثون لأنه يمنع الخيوط ويعمل كخيط واحد.
ملاحظة - لا تدعم Python تعدد العمليات لأن حزم الترابط لا تسمح لنا باستخدام مراكز وحدة المعالجة المركزية المتعددة.
لماذا يستخدم مطورو بايثون GIL؟
توفر Python ميزة العداد المرجعي الفريدة، والتي تُستخدم لإدارة الذاكرة. يحسب العداد المرجعي العدد الإجمالي للمراجع التي تم إجراؤها داخليًا في Python لتعيين قيمة لكائن بيانات. عندما يصل عدد المرجع إلى الصفر، يتم تحرير الذاكرة المخصصة للكائن. دعونا نرى المثال أدناه.
مثال -
كيفية فرز قائمة الصفيف في جافا
import sys a = [] b = a sys.getrefcount(a)
القلق الرئيسي بشأن متغير العدد المرجعي هو أنه يمكن أن يتأثر عندما يحاول خيطان أو ثلاثة سلاسل زيادة أو تقليل قيمته في وقت واحد. ومن المعروف باسم حالة السباق. في حالة حدوث هذه الحالة، قد يكون السبب هو تسرب الذاكرة التي لا يتم تحريرها مطلقًا. قد يحدث عطل أو أخطاء في برنامج بايثون.
يساعدنا GIL في إزالة مثل هذا الموقف باستخدام الأقفال لجميع هياكل البيانات المشتركة عبر سلاسل الرسائل بحيث لا يتم تغييرها بشكل غير متسق. توفر Python طريقة سهلة لتنفيذ GIL لأنها تتعامل مع إدارة الذاكرة الآمنة للخيط. يتطلب GIL تقديم قفل واحد لسلسلة المحادثات للمعالجة في Python. فهو يزيد من أداء البرنامج ذي الخيط الواحد حيث يتطلب الأمر قفلًا واحدًا فقط ليتم التعامل معه. كما أنه يساعد على إنشاء أي برنامج مرتبط بوحدة المعالجة المركزية (CPU) ويمنع حالة الجمود.
int إلى سلسلة Java
التأثير على برامج بايثون متعددة الخيوط
هناك فرق بين حدود وحدة المعالجة المركزية (CPU) في أدائها وحدود الإدخال/الإخراج المرتبطة ببرنامج Python النموذجي أو أي برنامج كمبيوتر. عادةً ما يتم دفع البرامج المرتبطة بوحدة المعالجة المركزية (CPU) إلى أقصى حدودها. تُستخدم هذه البرامج عمومًا في العمليات الحسابية الرياضية مثل ضرب المصفوفات، والحرق، ومعالجة الصور، وما إلى ذلك.
البرامج المرتبطة بالإدخال/الإخراج هي تلك البرامج التي تقضي وقتًا للحصول على المدخلات/المخرجات التي يمكن إنشاؤها بواسطة المستخدم أو الملف أو قاعدة البيانات أو الشبكة وما إلى ذلك. يجب أن تنتظر مثل هذه البرامج قدرًا كبيرًا من الوقت حتى يوفر المصدر المدخلات. من ناحية أخرى، المصدر لديه أيضًا وقت معالجة خاص به. على سبيل المثال - يفكر المستخدم فيما يجب إدخاله كمدخل.
دعونا نفهم المثال التالي.
مثال -
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 start_time = time.time() countdown(COUNT) end_time = time.time() print('Time taken in seconds -', end_time - start_time)
انتاج:
Time taken in seconds - 7.422671556472778
الآن نقوم بتعديل الكود أعلاه عن طريق تشغيل الخيطين.
مثال - 2:
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 thread1 = Thread(target=countdown, args=(COUNT//2,)) thread2 = Thread(target=countdown, args=(COUNT//2,)) start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
انتاج:
Time taken in seconds - 6.90830135345459
كما يمكننا أن نرى أن كلا الرمزين استغرقا نفس الوقت للانتهاء. منع GIL تنفيذ الخيوط المرتبطة بوحدة المعالجة المركزية بالتوازي في الكود الثاني.
جافا تحويل السلسلة إلى عدد صحيح
لماذا لم تتم إزالة GIL حتى الآن؟
العديد من المبرمجين لديهم شكوى بشأن هذا، لكن بايثون لا تستطيع جعل التغييرات ذات أهمية مثل إزالة GIL. سبب آخر هو أن GIL لم يتحسن حتى الآن. إذا تغير في بايثون 3، فإنه سيخلق بعض المشاكل الخطيرة. بدلاً من إزالة GIL، يمكن تحسين مفهوم GIL. وفقا لجويدو فان روسوم -
'أرحب بمجموعة من التصحيحات في Py3k فقط إذا لم ينخفض أداء البرنامج أحادي الترابط (ولبرنامج متعدد الخيوط ولكن مرتبط بالإدخال/الإخراج)'.
هناك أيضًا العديد من الطرق المتاحة التي تحل نفس المشكلة التي تم حلها بواسطة GIL، ولكن هناك طرق صعبة التنفيذ.
كيفية التعامل مع بايثون GIL
يعد استخدام المعالجة المتعددة هو الطريقة الأنسب لمنع البرنامج من GIL. تقدم بايثون مترجمين فوريين مختلفين لكل عملية يتم تشغيلها، لذلك في هذا السيناريو، يتم توفير مؤشر ترابط واحد لكل عملية في المعالجة المتعددة. دعونا نفهم المثال التالي.
مثال -
بنية البيانات
from multiprocessing import Pool import time COUNT = 50000000 def countdown(num): while num>0: num -= 1 if __name__ == '__main__': pool = Pool(processes=2) start_time = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
انتاج:
Time taken in seconds - 3.3707828521728516
قد يبدو أن الأداء اللائق قد تم زيادته ولكن إدارة العمليات لها نفقاتها العامة الخاصة والعمليات المتعددة أثقل من سلاسل العمليات المتعددة.
خاتمة
في هذا البرنامج التعليمي، ناقشنا GIL وكيف يمكننا استخدامه. إنه يعطي التحكم لخيط واحد للتنفيذ في الوقت المناسب. يغطي هذا البرنامج التعليمي أيضًا سبب أهمية GIL لمبرمجي Python.