تلقت بايثون الكثير من الاهتمام مؤخرًا. سيبدأ الإصدار 3.13، المقرر إصداره في أكتوبر من هذا العام، العمل الضخم لإزالة GIL. تم إطلاق الإصدار التجريبي بالفعل للمستخدمين الفضوليين الذين يرغبون في تجربة لغة بايثون (تقريبًا) بدون GIL.
كل هذا الضجيج جعلني أحفر في لغتي الخاصة، ArkScript، حيث كان لدي قفل VM عالمي أيضًا في الماضي (تمت إضافته في الإصدار 3.0.12، في عام 2020، وتمت إزالته في 3.1.3 في عام 2022)، إلى قارن الأشياء وأجبرني على التعمق أكثر في كيفية وسبب استخدام Python GIL.
قفل المترجم الشامل (GIL) هو آلية تستخدم في مترجمات لغة الكمبيوتر لمزامنة تنفيذ سلاسل العمليات بحيث يمكن لخيط أصلي واحد فقط (لكل عملية) تنفيذ العمليات الأساسية (مثل تخصيص الذاكرة وعد المراجع) في وقت واحد. وقت.
ويكيبيديا — قفل المترجم العالمي
التزامن هو عندما يمكن بدء مهمتين أو أكثر وتشغيلها وإكمالها في فترات زمنية متداخلة، ولكن هذا لا يعني أنه سيتم تشغيلهما في وقت واحد.
التوازي هو عندما يتم تشغيل المهام فعليًا في نفس الوقت، على سبيل المثال على معالج متعدد النواة.
للحصول على شرح متعمق، تحقق من إجابة Stack Overflow.
يمكن لـ GIL زيادة سرعة البرامج ذات الخيوط المفردة لأنك لا تحتاج إلى الحصول على الأقفال وتحريرها على جميع هياكل البيانات: يتم قفل المترجم بالكامل لذا فأنت آمن بشكل افتراضي.
ومع ذلك، نظرًا لوجود GIL واحد لكل مترجم، فهذا يحد من التوازي: تحتاج إلى إنشاء مترجم جديد تمامًا في عملية منفصلة (باستخدام وحدة المعالجة المتعددة بدلاً من الترابط) لاستخدام أكثر من نواة واحدة! وهذا له تكلفة أكبر من مجرد إنشاء سلسلة رسائل جديدة، لأنه يتعين عليك الآن القلق بشأن الاتصال بين العمليات، مما يضيف حملًا لا يمكن إهماله (راجع GeekPython - أصبح GIL اختياريًا في Python 3.13 للمعايير).
في حالة Python، يعود الأمر إلى التنفيذ الرئيسي، CPython، لعدم وجود إدارة آمنة للذاكرة. بدون GIL، قد يؤدي السيناريو التالي إلى إنشاء حالة سباق:
إذا تم تشغيل الخيط 1 أولاً، فسيكون العدد 11 (العدد * 2 = 10، ثم العد 1 = 11).
إذا تم تشغيل الخيط 2 أولاً، فسيكون العدد 12 (العد 1 = 6، ثم العد * 2 = 12).
ترتيب التنفيذ مهم، ولكن يمكن أن يحدث ما هو أسوأ من ذلك: إذا قرأ كلا الخيطين العدد في نفس الوقت، فسوف يمحو أحدهما نتيجة الآخر، وسيكون العدد إما 10 أو 6!
بشكل عام، وجود GIL يجعل تنفيذ (CPython) أسهل وأسرع في الحالات العامة:
كما أنه يجعل تغليف مكتبات C أسهل، لأنك تضمن سلامة سلاسل الرسائل بفضل GIL.
الجانب السلبي هو أن الكود الخاص بك هو غير متزامن كما في متزامن، ولكن غير متوازي.
[!ملحوظة]
Python 3.13 يقوم بإزالة GIL!أضاف PEP 703 تكوين المبنى --disable-gil بحيث يمكنك الاستفادة من تحسينات الأداء في البرامج متعددة الخيوط عند تثبيت Python 3.13.
في بايثون، يجب أن تأخذ الوظائف لونًا: فهي إما "عادية" أو "غير متزامنة". ماذا يعني هذا عمليا؟
>>> def foo(call_me): ... print(call_me()) ... >>> async def a_bar(): ... return 5 ... >>> def bar(): ... return 6 ... >>> foo(a_bar):2: RuntimeWarning: coroutine 'a_bar' was never awaited RuntimeWarning: Enable tracemalloc to get the object allocation traceback >>> foo(bar) 6
نظرًا لأن الوظيفة غير المتزامنة لا تُرجع قيمة على الفور، ولكنها تستدعي coroutine، فلا يمكننا استخدامها في كل مكان كرد اتصال، إلا إذا كانت الوظيفة التي نستدعيها مصممة لتلقي عمليات رد اتصال غير متزامنة.
لقد حصلنا على تسلسل هرمي للوظائف، لأن الوظائف "العادية" يجب أن تكون غير متزامنة لاستخدام الكلمة الأساسية المنتظرة، اللازمة لاستدعاء الوظائف غير المتزامنة:
can call normal -----------> normal can call async - -----------> normal | .-----------> async
بصرف النظر عن الثقة في المتصل، لا توجد طريقة لمعرفة ما إذا كان رد الاتصال غير متزامن أم لا (إلا إذا حاولت الاتصال به أولاً داخل كتلة محاولة/باستثناء للتحقق من وجود استثناء، ولكن هذا قبيح).
في البداية، كان ArkScript يستخدم Global VM Lock (أشبه بـ Python's GIL)، لأن وحدة http.arkm (المستخدمة لإنشاء خوادم HTTP) كانت متعددة الخيوط وتسببت في حدوث مشكلات مع ArkScript's VM عن طريق تغيير حالتها من خلال تعديل المتغيرات ووظائف الاتصال على سلاسل رسائل متعددة.
ثم في عام 2021، بدأت العمل على نموذج جديد للتعامل مع حالة الجهاز الافتراضي حتى نتمكن من موازنته بسهولة، وكتبت مقالًا عنه. تم تنفيذه لاحقًا بحلول نهاية عام 2021، وتمت إزالة قفل الجهاز الافتراضي العالمي.
لا يقوم ArkScript بتعيين لون للوظائف غير المتزامنة، لأنها غير موجودة في اللغة: إما أن يكون لديك وظيفة أو إغلاق، ويمكن لكليهما الاتصال ببعضهما البعض دون أي بناء جملة إضافي (الإغلاق هو كائن رجل فقير، في هذه اللغة: دالة تحمل حالة قابلة للتغيير).
يمكن جعل أي وظيفة غير متزامنة في موقع الاتصال (بدلاً من الإعلان):
(let foo (fun (a b c) ( a b c))) (print (foo 1 2 3)) # 6 (let future (async foo 1 2 3)) (print future) # UserType (print (await future)) # 6 (print (await future)) # nil
باستخدام غير المتزامن المدمج، نقوم بإنشاء مستقبل std::future تحت الغطاء (الاستفادة من std::async والخيوط) لتشغيل وظيفتنا في ضوء مجموعة من الوسائط. بعد ذلك يمكننا استدعاء الانتظار (مضمن آخر) والحصول على نتيجة وقتما نريد، مما سيؤدي إلى حظر مؤشر ترابط VM الحالي حتى تعود الوظيفة.
وبذلك يمكن الانتظار من أي دالة، ومن أي موضوع.
كل هذا ممكن لأن لدينا جهاز افتراضي واحد يعمل على حالة موجودة داخل Ark::internal::ExecutionContext، المرتبط بخيط واحد. تتم مشاركة VM بين المواضيع، وليس السياقات!
.---> thread 0, context 0 | ^ VM thread 1, context 1
عند إنشاء مستقبل باستخدام غير المتزامن، نحن:
يمنع هذا أي نوع من المزامنة بين سلاسل المحادثات نظرًا لأن ArkScript لا يكشف عن المراجع أو أي نوع من القفل الذي يمكن مشاركته (تم ذلك لأسباب تتعلق بالبساطة، حيث تهدف اللغة إلى أن تكون بسيطة إلى حد ما ولكن لا تزال قابلة للاستخدام).
ومع ذلك، فإن هذا الأسلوب ليس أفضل (ولا أسوأ) من أسلوب بايثون، حيث نقوم بإنشاء سلسلة رسائل جديدة لكل مكالمة، وعدد الخيوط لكل وحدة معالجة مركزية محدود، وهو أمر مكلف بعض الشيء. لحسن الحظ، لا أرى أن هذه مشكلة يجب معالجتها، حيث لا ينبغي أبدًا إنشاء مئات أو آلاف سلاسل المحادثات في وقت واحد أو استدعاء مئات أو آلاف وظائف بايثون غير المتزامنة في وقت واحد: فكلاهما سيؤدي إلى تباطؤ كبير في برنامجك.
في الحالة الأولى، سيؤدي ذلك إلى إبطاء العملية (حتى الكمبيوتر) حيث يعمل نظام التشغيل على توفير الوقت لكل موضوع؛ في الحالة الثانية، سيكون برنامج جدولة Python هو الذي سيتعين عليه التوفيق بين جميع coroutines الخاصة بك.
[!ملحوظة]
خارج الصندوق، لا يوفر ArkScript آليات لمزامنة سلسلة المحادثات، ولكن حتى إذا قمنا بتمرير UserType (وهو عبارة عن غلاف أعلى كائنات C تم مسح النوع ) إلى وظيفة، فإن الكائن الأساسي هو " منقول.
مع بعض الترميز الدقيق، يمكن للمرء إنشاء قفل باستخدام بنية UserType، مما يسمح بالمزامنة بين سلاسل الرسائل.(let lock (module:createLock)) (let foo (fun (lock i) { (lock true) (print (str:format "hello {}" i)) (lock false) })) (async foo lock 1) (async foo lock 2)
يستخدم ArkScript وPython نوعين مختلفين تمامًا من عدم المزامنة/الانتظار: الأول يتطلب استخدام غير متزامن في موقع الاتصال وينتج سلسلة رسائل جديدة بسياقها الخاص، بينما يتطلب الأخير من المبرمج وضع علامة على الوظائف على أنها غير متزامنة تكون قادرًا على استخدام الانتظار، وهذه الوظائف غير المتزامنة هي coroutines، تعمل في نفس مؤشر الترابط مثل المترجم.
أصلاً من lexp.lt
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3