في كتاب "بيثون. إلى آفاق التميز "يصف لوسيانو رامالو قصة واحدة. في عام 2000 ، تلقى لوسيانو دورات تدريبية ، وبمجرد أن نظر غيدو فان روسوم إلى الجمهور. بمجرد ظهور مثل هذا الحدث ، بدأ الجميع في طرح الأسئلة عليه. عندما سئل عن الوظائف التي اقترضتها بايثون من لغات أخرى ، أجاب غويدو: "كل ما هو جيد في بيثون سرق من لغات أخرى."
انها حقا. عاشت بايثون منذ فترة طويلة في سياق لغات البرمجة الأخرى وتمتص المفاهيم من بيئتها: تم استعارة التزامن ، وذلك بفضل تعبيرات Lisp lambda ، وتم نسخ تورنادو من libevent. ولكن إذا كان يجب على أي شخص أن يستعير الأفكار ، فهو إرلانج. تم إنشاؤه قبل 30 عامًا ، وجميع المفاهيم الموجودة في بيثون التي يتم تنفيذها حاليًا أو التي تم تحديدها للتو كانت تعمل في إرلانج لفترة طويلة: متعددة النواة ، الرسائل كأساس للتواصل ومكالمات الطريقة والتأمل داخل نظام الإنتاج المباشر. هذه الأفكار ، بشكل أو بآخر ، تجد التعبير عنها في أنظمة مثل
Seastar.io .
إذا لم تأخذ في الاعتبار "علوم البيانات" ، حيث أصبحت Python خارج المنافسة الآن ، فسيتم بالفعل تنفيذ كل شيء آخر في Erlang: العمل مع شبكة ، والتعامل مع HTTP ومآخذ الويب ، والعمل مع قواعد البيانات. لذلك ، من المهم لمطوري Python أن يفهموا أين ستنتقل اللغة: على طريق مرت بالفعل منذ 30 عامًا.
لفهم تاريخ تطور اللغات الأخرى وفهم تقدم التقدم ، قمنا بدعوة
Maxim Lapshin (
erlyvideo ) ، مؤلف مشروع Erlyvideo.ru ، إلى
Moscow Python Conf ++ .
في الأسفل ، توجد نسخة نصية من هذا التقرير ، وهي: في أي اتجاه يُجبر النظام على التطوير ، والذي يستمر في الانتقال من رمز خطي بسيط إلى libevent وما بعده ، وهو أمر شائع وما هي الاختلافات بين Elixir و Python. سنولي اهتمامًا خاصًا لكيفية إدارة مآخذ التوصيل والخيوط والبيانات بلغات ومنصات برمجة مختلفة.
يحتوي Erlyvideo.ru على نظام مراقبة فيديو يتم فيه كتابة التحكم في الوصول للكاميرات في Python. هذه مهمة كلاسيكية لهذه اللغة. هناك مستخدمون وكاميرات ومقاطع فيديو يمكنهم مشاهدتها: يرى شخص ما بعض الكاميرات ، بينما يرى آخرون موقعًا منتظمًا.
تم اختيار Python لأنها ملائمة لكتابة مثل هذه الخدمة عليها: هناك أطر عمل ، ORMs ، مبرمجون ، بعد كل شيء. يتم حزم البرامج المتقدمة وبيعها للمستخدمين. Erlyvideo.ru هي شركة تبيع البرامج ، ولا تقدم الخدمة فقط.
ما هي المشاكل مع بيثون أريد حلها.
لماذا هناك مثل هذه المشاكل مع multicore؟ ركضنا Flussonic على أجهزة الكمبيوتر الملاعب حتى قبل أن تفعل. لكن بيثون تواجه صعوبات في هذا: لماذا لا تزال لا تستخدم جميع النوى الـ 80 لخوادمنا للعمل؟
كيف لا تعاني من مآخذ مفتوحة؟ مراقبة عدد مآخذ مفتوحة مشكلة كبيرة. عندما يصل إلى الحد ، أغلق وقم بمنع التسرب أيضًا.
هل لدى المتغيرات العالمية المنسية حلاً؟ تسريب المتغيرات العامة هو الجحيم لأي لغة تجميع للقمامة مثل Java أو C #.
كيفية استخدام الحديد دون هدر الموارد؟ كيفية الحصول على دون تشغيل 40 عاملا Jung و 64 جيجابايت من ذاكرة الوصول العشوائي إذا كنا نريد استخدام الخوادم بكفاءة وعدم رمي مئات الآلاف من الدولارات شهريا على الأجهزة غير الضرورية؟
لماذا هناك حاجة متعددة النواة
ولكي يتم استخدام جميع النوى بالكامل ، هناك حاجة إلى عمال أكثر من النوى. على سبيل المثال ، بالنسبة لـ 40 مركزًا للمعالجات ، هناك حاجة إلى 100 عامل: ذهب عامل واحد إلى قاعدة البيانات ، والآخر مشغول بشيء آخر.
عامل واحد يمكن أن تستهلك 300-400 ميغابايت . ما زلنا نكتب هذا في Python ، وليس في Ruby on Rails ، التي يمكن أن تستهلك عدة مرات أكثر ، وسوف يضيع 40 جيجابايت من ذاكرة الوصول العشوائي بسهولة ويسر. أنها ليست مكلفة للغاية ، ولكن لماذا شراء الذاكرة حيث لا يمكنك شراء.
متعدد النواة يساعد على تبخير البيانات المشتركة وتقليل استهلاك الذاكرة ، وتشغيل مريح وآمن العديد من العمليات المستقلة. من الأسهل بكثير البرمجة ، ولكنها أكثر تكلفة من الذاكرة.
إدارة المقبس
على مقبس الويب ، نقوم باستطلاع بيانات وقت تشغيل الكاميرات من الجهة الخلفية. يتصل برنامج Python بـ Flussonic ويستطلع بيانات حالة الكاميرات: سواء كانت تعمل أم لا ، هل توجد أي أحداث جديدة.
من ناحية أخرى ، يتصل العميل ، ومن خلال مقبس الويب ، نرسل هذه البيانات إلى المستعرض. نريد نقل بيانات العميل في الوقت الحقيقي: الكاميرا قيد التشغيل وإيقاف التشغيل ، أكلت القط ، نمت ، تمزيق أريكة ، الضغط على الزر ودفع القط بعيدا.
ولكن ، على سبيل المثال ، حدث نوع من المشكلة: قاعدة البيانات لم تستجب للطلب ، سقطت جميع الشفرات ، وكان هناك مآخذان مفتوحان. بدأنا إعادة التحميل ، وفعلنا شيئًا ، مرة أخرى هذه المشكلة - كان هناك مآخذ. تم معالجة خطأ DB بشكل غير صحيح وتم تعليق اتصالين مفتوحين. مع مرور الوقت ، وهذا يؤدي إلى تسرب مأخذ التوصيل.
متغيرات عالمية منسية
قدمت إملاء عالمي لقائمة المتصفحات المتصلة عبر مقبس الويب. يقوم شخص بتسجيل الدخول إلى الموقع ، نفتح له مقبس ويب له. ثم نضع مقبس الويب بمعرفه في نوع ما من الإملاء العام ، ويتبين أن هناك نوعًا من الأخطاء يحدث.
على سبيل المثال ، سجلوا رابط اتصال في dict لإرسال البيانات.
حدث استثناء ، نسيت حذف الرابط وتعليق البيانات . لذلك بعد مرور بعض الوقت ، بدأ تفويت 64 جيجا بايت ، وأريد مضاعفة الذاكرة على الخادم. هذا ليس حلاً ، لأن البيانات سوف تتسرب على أي حال.
نرتكب دائمًا أخطاء - نحن أشخاص ولا يمكننا تتبع كل شيء.
والسؤال هو أن بعض الأخطاء تحدث ، حتى تلك التي لم نتوقع رؤيتها.
رحلة تاريخية
للوصول إلى الموضوع الرئيسي ، دعونا نتعمق في القصة. كل ما نتحدث عن Python و Go و Erlang عنه الآن ، ذهب أشخاص آخرون بهذه الطريقة منذ حوالي 30 عامًا. نحن في بيثون نقطع شوطًا طويلًا ونملأ المطبات التي مرت بالفعل منذ عقود. يكرر المسار بطريقة مذهلة.
DOS
أولاً ، دعنا ننتقل إلى DOS ، إنه الأقرب. قبله كانت هناك أشياء مختلفة تمامًا وليس الجميع على قيد الحياة الذي يتذكر أجهزة الكمبيوتر قبل DOS.
احتل برنامج DOS الكمبيوتر (تقريبًا) بشكل حصري . أثناء تشغيل لعبة ، على سبيل المثال ، لا يتم تنفيذ أي شيء آخر. لن تستخدم الإنترنت - لم تصل بعد ، ولن تصل إلى أي مكان. كان حزينا ، لكن ذكرياته دافئة ، لأنه مرتبط بالشباب.
تعدد المهام التعاونية
نظرًا لأنه كان مؤلمًا بالفعل مع DOS ، ظهرت تحديات جديدة ، وأصبحت أجهزة الكمبيوتر أكثر قوة.
منذ عقود ، طوروا مفهوم تعدد المهام التعاوني ، حتى قبل Windows 3.11.
يتم فصل البيانات عن طريق العمليات ، ويتم تنفيذ كل عملية على حدة: فهي محمية بطريقة أو بأخرى من بعضها البعض. لن تتمكن الشفرة السيئة في إحدى العمليات من إفساد الشفرة في المستعرض (ثم ظهرت المتصفحات الأولى بالفعل).
السؤال التالي هو: كيف سيتم توزيع وقت الحوسبة بين العمليات المختلفة؟ ثم لم يكن هناك أكثر من نواة ، بل كان هناك نظام مزدوج المعالج. كان هذا المخطط: في حين ذهبت عملية واحدة ، على سبيل المثال ، إلى قرص للبيانات ، فإن العملية الثانية تتلقى السيطرة من نظام التشغيل. الأولى ستكون قادرة على السيطرة عندما يعطي الثاني نفسه طوعا. أبسط الموقف إلى حد كبير ، لكن
العملية سمحت طوعًا بطريقة ما بإزالته من المعالج .
تعدد المهام الاستباقية
أدت المهام المتعددة التعاونية إلى المشكلة التالية: يمكن أن تتوقف العملية لأنها مكتوبة بشكل سيء.
إذا استغرق المعالج وقتًا طويلاً في المعالجة ، فإنه يحظر الباقي . في هذه الحالة ، تعطل جهاز الكمبيوتر ، ولا يمكن القيام بأي شيء ، على سبيل المثال ، تبديل النافذة.
استجابة لهذه المشكلة ، تم اختراع المهام المتعددة الوقائية. يعمل نظام التشغيل الآن على تشغيل محركات الأقراص بشكل صارم: يزيل العمليات من التنفيذ ويفصل بياناتها تمامًا ويحمي ذاكرة العملية من بعضها البعض ويمنح الجميع قدراً من الوقت الحسابي.
يخصص نظام التشغيل الفواصل الزمنية نفسها لكل عملية .
مسألة الوقت sheduling لا يزال مفتوحا. اليوم ، ما زال مطورو نظام التشغيل يتوصلون إلى ما هو صحيح ، وبأي ترتيب ، ولمن ، وكم من الوقت لمنح للإدارة. اليوم نرى تطور هذه الأفكار.
تيارات
لكن هذا لم يكن كافيا. تحتاج العمليات إلى تبادل البيانات: من خلال الشبكة مكلفة ، بطريقة ما لا تزال معقدة. لذلك ، اخترع
مفهوم التدفقات .
مؤشرات الترابط هي عمليات خفيفة الوزن تشترك في ذاكرة مشتركة.
تم إنشاء تيارات على أمل أن يكون كل شيء سهلًا وبسيطًا وممتعًا. الآن
تعتبر البرمجة متعددة الخيوط antipattern . إذا كان منطق العمل مكتوبًا في سلاسل الرسائل ، فمن المرجح أن يتم التخلص من هذا الرمز ، لأنه من المحتمل وجود أخطاء فيه. إذا بدا لك أنه لا توجد أخطاء ، فأنت ببساطة لم تعثر عليها بعد.
البرمجة متعددة مؤشرات الترابط أمر معقد للغاية. هناك عدد قليل من الناس الذين كرسوا أنفسهم حقًا للقدرة على الكتابة على سلاسل الرسائل ويحصلون على شيء مفيد حقًا.
وفي الوقت نفسه ، ظهرت
أجهزة الكمبيوتر متعددة النواة . لقد أحضروا معهم أشياء فظيعة. لقد اتبعت طريقة مختلفة تمامًا للبيانات ، فقد نشأت أسئلة حول مكان البيانات ، والآن عليك أن تفهم من أي نواة تذهب إليها.
يحتاج أحد النوى إلى وضع البيانات هنا ، والآخر هناك ، ولا يخلط بين هذه الأشياء بأي حال من الأحوال ، لأن المجموعات ظهرت بالفعل داخل الكمبيوتر. داخل الكمبيوتر الحديث ، هناك كتلة عندما يتم لحام جزء من الذاكرة بنواة واحدة والأخرى إلى أخرى. يمكن أن يختلف وقت العبور بين هذه البيانات حسب أوامر الحجم.
أمثلة بيثون
فكر في مثال بسيط على "الخدمة لمساعدة العميل". إنه يختار أفضل سعر للبضائع على العديد من المنصات: نحن نقود باسم البضائع ونبحث عن الأرضيات التجارية بأقل سعر ممكن.
هذا هو الكود الموجود في جانغو القديمة ، بيثون 2. اليوم ، لا تحظى بشعبية كبيرة ، حيث يبدأ عدد قليل من الناس في تنفيذ مشاريع.
@api_view(['GET']) def best_price(request): name = request.GET['name'] price1 = http_fetch_price('market.yandex.ru', name) price2 = http_fetch_price('ebay.com', name) price3 = http_fetch_price('taobao.com', name) return Response(min([price1,price2,price3]))
يصل الطلب ، نذهب إلى خلفية واحدة ، ثم إلى أخرى. في الأماكن التي يتم فيها
http_fetch_price
، يتم حظر
http_fetch_price
. في هذه اللحظة ، يشرع العامل بأكمله في رحلة إلى Yandex.Market ، ثم إلى eBay ، ثم إلى مهلة في تاوباو ، وفي النهاية يعطي إجابة.
كل هذا الوقت يقف العامل بأكمله .
من الصعب للغاية استطلاع آراء متعددة في نفس الوقت. هذا وضع سيئ: يتم استهلاك الذاكرة وإطلاق عدد كبير من العمال ومراقبة الخدمة بأكملها. من الضروري أن ننظر إلى مدى تكرار هذه الطلبات ، هل ما زلت بحاجة إلى إدارة العمال أو هل هناك طلبات إضافية مرة أخرى. هذه هي المشاكل ذاتها التي تحدثت عنها.
من الضروري استجواب العديد من الوصلات الخلفية بدورها .
ماذا نرى في بايثون؟
عملية واحدة لكل مهمة ، في بيثون لا يوجد حتى الآن multicore. الموقف واضح: في لغات هذا الفصل ، يصعب إنشاء مولتيكوري آمن بسيط ، لأنه
سيقتل الأداء .
إذا ذهبت إلى الإملاء من تدفقات مختلفة ، فيمكنك الوصول إلى البيانات على هذا النحو: الغراء حالتان من بيثون في الذاكرة حتى يقوما بتفتيش البيانات - إنهما ببساطة يكسرانها. على سبيل المثال ، للذهاب إلى الإملاء وليس كسر أي شيء ، تحتاج إلى وضع المزامير أمامه. إذا كان هناك كائن مزيف قبل كل أمر ، فسيتباطأ النظام حوالي 1000 مرة - سيكون ببساطة غير مريح. من الصعب جرها إلى متعددة النواة.
لدينا
مؤشر ترابط واحد فقط من التنفيذ والعمليات فقط يمكن أن نطاق . في الواقع ، قمنا بإعادة اختراع DOS داخل العملية - لغة البرمجة النصية لعام 2010. يوجد داخل العملية شيء يشبه DOS: بينما نقوم بعمل ما ، فإن جميع العمليات الأخرى لا تعمل. لا أحد يحب التجاوزات الضخمة في التكلفة والاستجابة البطيئة.
ظهرت
مفاعلات المقبس في بيثون منذ بعض الوقت ، على الرغم من أن الفكرة نفسها ولدت منذ فترة طويلة. الآن يمكنك أن تتوقع استعداد عدة مآخذ في وقت واحد.
في البداية ، أصبح المفاعل في الطلب على خوادم مثل nginx. بما في ذلك الاستخدام الصحيح لهذه التكنولوجيا ، فقد أصبحت شائعة. ثم زحف المفهوم إلى لغات البرمجة النصية مثل بيثون وروبي.
فكرة المفاعل هي أننا انتقلنا إلى البرمجة الموجهة نحو الحدث.
الحدث الموجه البرمجة
سياق تنفيذ واحد ينتج طلب. أثناء انتظار الإجابة ، يتم تنفيذ سياق مختلف. من الجدير بالذكر أننا مررنا تقريبًا بمرحلة التطور نفسها التي مرت بها عملية الانتقال من DOS إلى Windows 3.11. لم يفعل ذلك إلا قبل 20 عامًا ، وفي بيثون وروبي ظهر قبل 10 سنوات.
ملفوف
هذا هو إطار شبكة يحركها الحدث. ظهرت في عام 2002 وكتبت في بيثون. أخذت المثال أعلاه وأعد كتابته على Twisted.
def render_GET(self, request): price1 = deferred_fetch_price('market.yandex.ru', name) price2 = deferred_fetch_price('ebay.com', name) price3 = deferred_fetch_price('taobao.com', name) dl = defer.DeferredList([price1,price2,price3]) def reply(prices): request.write('%d'.format(min(prices))) request.finish() dl.addCallback(reply) return server.NOT_DONE_YET
قد تكون هناك أخطاء ، وعدم الدقة ، ومعالجة الأخطاء سيئة السمعة ليست كافية. لكن المخطط التقريبي هو هذا: نحن لا نقدم طلبًا ، ولكن نطلب الذهاب لهذا الطلب في وقت لاحق ، عندما يكون هناك وقت. في السطر مع
defer.DeferredList
نريد أن نجمع ردودًا من عدة استعلامات معًا.
في الواقع ، يتكون الكود من جزأين. في الجزء الأول ، ماذا حدث قبل الطلب ، وفي الجزء الثاني ، ماذا بعد.
إن التاريخ الكامل للبرمجة الموجهة نحو الحدث مشبع بألم كسر الشفرة الخطية على "قبل الطلب" و "بعد الطلب".
هذا مؤلم لأن أجزاء الكود مختلطة: لا يزال يتم تنفيذ الأسطر الأخيرة في الطلب الأصلي ، وسيتم استدعاء وظيفة
reply
بعد.
ليس من السهل أن نضع في اعتبارنا على وجه التحديد لأننا اخترقنا الكود الخطي ، لكن يجب القيام به. دون الخوض في التفاصيل ، فإن الكود الذي أعيد كتابته من جانغو إلى تويستيد
سوف ينتج تسارعًا زائفًا لا يصدق تمامًا .
فكرة ملتوية
يمكن تنشيط كائن عندما يكون المقبس جاهزًا.
نأخذ الكائنات التي نجمع فيها البيانات اللازمة من السياق ونربط تنشيطها بالمقبس. يعد توفر المقبس الآن أحد أهم عناصر التحكم للنظام بأكمله. الكائنات ستكون سياقاتنا.
ولكن في الوقت نفسه ، لا تزال اللغة تفصل مفهوم سياق التنفيذ الذي تعيش فيه الاستثناءات.
يعيش سياق التنفيذ بشكل منفصل عن الكائنات ويتم ربطه بشكل فضفاض . تكمن المشكلة هنا في حقيقة أننا نحاول جمع البيانات داخل الكائنات: لا توجد طريقة بدونها ، لكن اللغة لا تدعمها.
كل هذا يؤدي إلى الجحيم رد الاتصال الكلاسيكية. لماذا ، على سبيل المثال ، يحبون Node.js - حتى وقت قريب ، لم تكن هناك طرق أخرى على الإطلاق ، ولكن لا يزال يظهر في بيثون. المشكلة هي أن هناك
فواصل للكود عند نقاط IO الخارجية التي تؤدي إلى معاودة الاتصال.
هناك العديد من الأسئلة. هل من الممكن "الغراء" حواف الفجوة في الكود؟ هل من الممكن العودة إلى الكود البشري العادي؟ ماذا تفعل إذا كان هناك كائن منطقي يعمل بمقبسين وأغلق أحدهما؟ كيف لا تنسى أن تغلق الثانية؟ هل من الممكن استخدام كل النوى بطريقة أو بأخرى؟
غير متزامن IO
الإجابة الجيدة على هذه الأسئلة هي Async IO. هذه خطوة كبيرة للأمام ، رغم أنها ليست سهلة. Async IO شيء معقد ، يوجد أسفل الغطاء العديد من الفروق الدقيقة المؤلمة.
async def best_price(request): name = request.GET['name'] price1 = async_http_fetch_price('market.yandex.ru', name) price2 = async_http_fetch_price('ebay.com', name) price3 = async_http_fetch_price('taobao.com', name) prices = await asyncio.wait([price1,price2,price3]) return min(prices)
فجوة الشفرة مخفية تحت بناء الجملة
async/await
. أخذنا كل ما كان من قبل ، لكننا لم نذهب إلى الشبكة في هذا الرمز. لقد أزلنا
Callback(reply)
، والذي كان في المثال السابق ، وأخفاه في
await
- المكان الذي سيتم فيه قص الشفرة بالمقص. سيتم تقسيمها إلى قسمين: الجزء المتصل وجزء رد الاتصال ، والذي يعالج النتائج.
هذا هو
السكر النحوي الكبير . هناك طرق لصق توقعات متعددة في واحد. هذا رائع ، لكن هناك فارق بسيط:
يمكن كسر كل شيء بواسطة مقبس "كلاسيكي" . في Python ، لا يزال هناك عدد كبير من المكتبات التي تذهب إلى المقبس بشكل متزامن ، وتقوم بإنشاء
timer library
وتدمير كل شيء لك. كيفية تصحيح هذا ، أنا لا أعرف.
لكن
عدم التزامن لا يساعد في التسريبات والمتعددة . لذلك ، لا توجد تغييرات أساسية ، رغم أنها أصبحت أفضل.
لا يزال لدينا جميع المشاكل التي تحدثنا عنها في البداية:
- من السهل أن تسرب مع مآخذ.
- من السهل ترك الروابط في المتغيرات العالمية ؛
- معالجة الأخطاء المضنية للغاية ؛
- ما زال من الصعب القيام به متعدد النواة.
ما يجب القيام به
ما إذا كان هذا سيتطور جميعًا ، لا أعرف ، لكنني سأعرض التنفيذ بلغات ومنصات أخرى.
سياقات التنفيذ المعزولة. في سياقات التنفيذ ، يتم تجميع النتائج ، يتم الاحتفاظ بالمآخذ: كائنات منطقية نقوم فيها عادةً بتخزين جميع البيانات حول عمليات الاسترجاعات والمقابس. أحد المفاهيم: خذ سياقات التنفيذ ، وقم بإلصاقها بخيوط التنفيذ ، وعزلها تمامًا عن بعضها البعض.
نموذج التحول من الأشياء. دعنا نربط السياق بخيط التنفيذ. هناك نظائرها ، وهذا ليس شيئا جديدا. إذا حاول شخص ما تحرير شفرة مصدر Apache وكتابة وحدات لهم ، فهو يعلم أن هناك مجموعة Apache.
لا يسمح الروابط بين تجمع اباتشي. توجد بيانات من تجمع Apache واحد - التجمع المقترن بالطلبات ، بداخله ، ولا يمكنك الحصول على أي شيء منه.
من الناحية النظرية ، يكون ذلك ممكنًا ، ولكن إذا قمت بذلك ، إما أن يوبخ أحدهم ، أو لن يقبل التصحيح ، أو سيكون لديه تصحيح طويل ومؤلمة للإنتاج. بعد ذلك ، لن يقوم أحد بذلك وسيسمح للآخرين بالقيام بمثل هذه الأشياء. من المستحيل ببساطة الرجوع إلى البيانات بين السياقات بعد الآن ، هناك حاجة لعزل كامل.
كيفية تبادل النشاط؟ ما نحتاج إليه ليس الموناديات الصغيرة ، التي يتم إغلاقها داخلها ولا تتواصل مع بعضها البعض. نحن بحاجة لهم للتواصل. نهج واحد هو الرسائل. هذا هو المسار الذي سلكه Windows تقريبًا عند تبادل الرسائل بين العمليات. في نظام التشغيل العادي ، لا يمكنك إعطاء رابط لذاكرة عملية أخرى ، لكن يمكنك الإشارة عبر الشبكة ، كما في نظام UNIX ، أو من خلال الرسائل ، كما في نظام Windows.
جميع الموارد داخل العملية والسياق تصبح خيط التنفيذ . نحن لصقها معا:
- بيانات وقت التشغيل في جهاز ظاهري تحدث فيه استثناءات ؛
- مؤشر ترابط التنفيذ ، مثل ما يتم تنفيذه على المعالج ؛
- كائن يتم فيه تجميع كل البيانات بطريقة منطقية.
تهانينا - لقد اخترعنا UNIX داخل لغة برمجة! تم اختراع هذه الفكرة حوالي عام 1969. حتى الآن ، لم يكن في بيثون حتى الآن ، ولكن بيثون من المرجح أن يأتي إلى هذا. وربما لن تأتي - لا أعرف.
ماذا يعطي
بادئ ذي
بدء ،
التحكم التلقائي في الموارد .
قالوا في Moscow Python Conf ++ 2019 أنه يمكنك كتابة برنامج على Go ومعالجة جميع الأخطاء. سوف يقف البرنامج وكأنه قفاز ويعمل لعدة أشهر. هذا صحيح ، لكننا لا نتعامل مع جميع الأخطاء.
نحن أشخاص أحياء ، ولدينا دائمًا مواعيد نهائية ، والرغبة في القيام بشيء مفيد ، وعدم معالجة الخطأ 535 لهذا اليوم. لا تتسبب الشفرة المليئة بمعالجة الأخطاء في الشعور بالمشاعر الدافئة لدى أي شخص.
لذلك ، نكتب جميعًا "طريقًا سعيدًا" ، ومن ثم سنكتشفه في الإنتاج. لنكن صادقين: فقط عندما تحتاج إلى معالجة شيء ما ، فإننا نبدأ في المعالجة. البرمجة الدفاعية مختلفة قليلاً ، وليست تنمية تجارية.
لذلك ،
عندما يكون لدينا تحكم تلقائي عن الأخطاء - فهذا جيد . لكن أنظمة التشغيل جاءت قبل 50 عامًا: إذا ماتت بعض العمليات ، فسيتم إغلاق كل شيء يفتح تلقائيًا. اليوم لا يحتاج أي شخص إلى كتابة التعليمات البرمجية التي من شأنها تنظيف الملفات وراء العملية التي تم قتلها. لم يكن هذا موجودًا منذ 50 عامًا في أي نظام تشغيل ، ولكن في بيثون لا تزال بحاجة إلى اتباع هذا بعناية وبعناية بيديك. هذا غريب.
يمكنك أن تأخذ الحوسبة الثقيلة في سياق مختلف ، ولكن يمكن بالفعل الانتقال إلى نواة أخرى. شاركنا البيانات ، لم نعد نحتاج إلى مزاعم. يمكنك إرسال البيانات في سياق مختلف ، قل: "ستقوم بذلك في مكان ما ، ثم أخبرني أنك قد انتهيت من القيام بشيء ما."
تطبيق غير متزامن دون عبارة "async / تنتظر" . مزيد من المساعدة قليلا من الجهاز الظاهري ، من وقت التشغيل. هذا ما تحدثنا عنه مع
async/await
: يمكنك أيضًا التحويل إلى الرسائل وإزالة
async/await
والحصول عليها على مستوى الجهاز الظاهري.
عمليات إرلانج
تم ابتكار Erlang قبل 30 عامًا. نظر الرجال الملتحون ، الذين لم يكونوا ملتحين جدًا في ذلك الوقت ، إلى UNIX ونقلوا جميع المفاهيم إلى لغة البرمجة. لقد قرروا أن لديهم الآن ما ينامون به ليلاً وأن يذهبوا بصيد السمك بدون كمبيوتر. ثم لم تكن هناك أجهزة كمبيوتر محمولة بعد ، لكن الرجال الملتحيين عرفوا بالفعل أنه ينبغي التفكير في ذلك مسبقًا.
حصلنا على Erlang (Elixir) - سياقات نشطة تُنفذ نفسها . كذلك مثالي على Erlang. في Elixir ، تبدو متشابهة ، مع بعض الاختلافات.
best_price(Name) -> Price1 = spawn_price_fetcher('market.yandex.ru', Name), Price2 = spawn_price_fetcher('ebay.com', Name), Price3 = spawn_price_fetcher('taobao.com', Name), lists:min(wait4([Price1,Price2,Price3])).
نطلق العديد من أدوات جلب - هذه هي العديد من السياقات الجديدة المنفصلة التي ننتظرها. انتظروا وجمعوا البيانات وأعادوا النتيجة كحد أدنى للسعر. كل هذا يشبه
async/await
، ولكن بدون عبارة "المزامنة / الانتظار".
ملامح إكسير
يقع Elixir في قاعدة Erlang ، ويتم نقل جميع مفاهيم اللغة بهدوء إلى Elixir. ما هي معالمه؟
حظر الروابط عبر المعالج. أعني العملية عملية خفيفة الوزن داخل سياق افتراضي للجهاز. المبسطة ، إذا تم نقلها إلى Python ، فإن ارتباطات البيانات داخل كائن آخر محظورة في Erlang. يمكن أن يكون لديك رابط للكائن بالكامل كمربع مغلق ، لكن لا يمكنك الرجوع إلى البيانات الموجودة داخله. لا يمكنك حتى الحصول على مؤشر إلى البيانات الموجودة داخل كائن آخر. يمكنك فقط معرفة الكائن نفسه.
لا توجد عمليات مزامنة داخل العمليات (الكائنات). هذا أمر مهم - شخصيًا ، لا أريد أبدًا أن أتقاطع في حياتي مع تاريخ تصحيح أخطاء الرحلات المتعددة الخيوط إلى الإنتاج. لا أتمنى هذا لأي شخص.
يمكن أن تتحرك العمليات حول النوى ، إنها آمنة. لم نعد بحاجة لتجاوز ، كما في Java ، مجموعة من
pointer
الأخرى وإعادة كتابتها عند نقل البيانات من مكان إلى آخر: ليس لدينا بيانات مشتركة وروابط داخلية. على سبيل المثال ، من أين تأتي مشكلة نعومة الورك؟ بسبب حقيقة أن شخصا ما يشير إلى هذه البيانات.
إذا قمنا بنقل البيانات داخل الكومة إلى موقع آخر للضغط ، فنحن بحاجة إلى المرور عبر النظام بأكمله. يمكن أن تشغل عشرات الجيجابايت وتحديث جميع المؤشرات - وهذا مجنون.
سلامة الخيط الكامل ، ويرجع ذلك إلى حقيقة أن كل الاتصالات يمر الرسائل. عند استسلام كل هذا ، حصلنا
على عملية إزالة مزاحمة . لقد حصل عليها بسهولة ورخيصة.
الرسائل كأساس للتواصل. كائنات داخلية ومكالمات الوظائف العادية وبين كائنات الرسالة. وصول البيانات من الشبكة هو رسالة ، واستجابة كائن آخر هي رسالة ، وشيء آخر في الخارج هو أيضًا رسالة في قائمة انتظار واردة. هذا ليس على UNIX لأنه لم يتجذر.
يدعو الأسلوب. لدينا الكائنات التي نسميها العمليات. يتم استدعاء أساليب العمليات من خلال الرسائل.
طرق الاتصال هي أيضا إرسال رسالة. انه لشيء رائع أنه الآن يمكن القيام به مع مهلة. إذا كان هناك شيء يجيب علينا ببطء ، فإننا نسمي الأسلوب على كائن آخر. لكن في الوقت نفسه ، نقول إننا على استعداد للانتظار أكثر من 60 ثانية ، لأن لدي عميل به مهلة 70 ثانية. سأحتاج للذهاب وأقول له "503" - تعال غدًا ، والآن لا ينتظرونك.
علاوة على ذلك ،
يمكن تأجيل الرد على المكالمة . داخل الكائن ، يمكنك قبول طلب الاتصال بالطريقة ، وقول: "نعم ، نعم ، سوف أضعك الآن ، أعود لنصف ساعة ، سأجيب عليك." لا يمكنك التحدث ، ولكن بصمت جانبا. نستخدمها في بعض الأحيان.
كيفية العمل مع الشبكة؟
يمكنك كتابة التعليمات البرمجية الخطية أو عمليات الاسترجاعات أو بأسلوب
asyncio.gather
. مثال على كيف سيبدو هذا.
wait4([ ]) -> [ ]; wait4(List) -> receive {reply, Pid, Price} -> [Price] ++ wait4(List -- [Pid]) after 60000 -> [] end.
في دالة
wait4
من المثال السابق ،
wait4
على قائمة الأشخاص الذين ما زلنا في انتظار الإجابات. في حالة استخدام طريقة
receive
نحصل على رسالة من تلك العملية ، نكتبها إلى القائمة. إذا انتهت القائمة ، فإننا نرجع كل ما كان وجمع القائمة. لقد طلبنا في الوقت نفسه ثلاثة أشياء لدفع البيانات إلينا. إذا لم يتمكنوا من الإدارة معًا في 60 ثانية ، ولم يرد واحد منهم على الأقل على "موافق" ، فستكون لدينا قائمة فارغة. ولكن من المهم أن يكون لدينا مهلة عامة لطلب فورًا لمجموعة كاملة من الكائنات.
قد يقول شخص ما ، "فكر ، libcurl لديه نفس الشيء." ولكن من المهم هنا أنه من ناحية أخرى ، لا يمكن أن يكون هناك فقط رحلة HTTP ، ولكن أيضًا رحلة DB ، وكذلك بعض العمليات الحسابية ، على سبيل المثال ، حساب نوع ما من الرقم الأمثل للعميل.
خطأ في التعامل
لقد مرت الأخطاء من الدفق إلى الكائن ، والتي هي الآن واحدة واحدة . الآن يصبح الخطأ نفسه مرفقًا ليس بسلسلة الرسائل ، ولكن بالكائن الذي تم تنفيذه فيه.
هذا هو أكثر منطقية بكثير. عادة ، عندما نرسم جميع أنواع المربعات والدوائر الصغيرة على السبورة على أمل أن تعود إلى الحياة وتبدأ في جلب النتائج والمال ، فنحن عادةً ما نرسم كائنات ، وليس التدفقات التي سيتم تنفيذ هذه الأشياء فيها. على سبيل المثال ، عند التسليم ، يمكننا استلام رسالة تلقائية
حول وفاة كائن آخر .
التأمل أو التصحيح في الإنتاج
ما الذي يمكن أن يكون أجمل من الذهاب إلى المنتج والخصم ، لا سيما إذا كان الخطأ يحدث فقط تحت الحمل خلال ساعات الذروة. في ساعة الذروة نقول:
- هيا ، سأعود الآن!- اخرج من الباب وهناك إعادة تشغيل لشخص آخر!هنا يمكننا أن نذهب داخل نظام المعيشة الذي يعمل الآن وليس على استعداد لهذا الغرض. للقيام بذلك ، لا تحتاج إلى إعادة تشغيله مع منشئ ملفات التعريف ، مع مصحح الأخطاء ، إعادة الإنشاء.
بدون أي خسارة في الأداء في نظام الإنتاج المباشر ، يمكننا أن نرى قائمة من العمليات: ما بداخلها ، وكيف يعمل كل شيء ، ونفاياتها ، والتحقق مما يحدث لهم. كل هذا مجاني من خارج منطقة الجزاء.
المكافآت
الكود هو السوبر موثوقة. على سبيل المثال ، لدى Python هشاشة مع عدم
old vs async
، وستبقى لمدة خمس سنوات ، وليس أقل. بالنظر إلى السرعة التي تم بها تطبيق Python 3 ، يجب ألا تأمل أن تكون سريعة.
قراءة وتتبع الرسائل أسهل من تصحيح عمليات الاسترجاعات . هذا مهم. يبدو أنه إذا كان لا يزال لدينا عمليات رد اتصال لمعالجة الرسائل التي يمكننا رؤيتها ، فما الأفضل؟ من خلال حقيقة أن الرسائل هي جزء من البيانات في الذاكرة. يمكنك أن تنظر إليها بعيون وتفهم ما قد جاء هنا. يمكن إضافته إلى التتبع ، والحصول على قائمة الرسائل في ملف نصي. هذا هو أكثر ملاءمة من عمليات الاسترجاعات.
رائع متعدد النواة ، وإدارة الذاكرة
والتأمل داخل نظام الإنتاج
المباشر .
المشاكل
بطبيعة الحال ، Erlang لديه أيضا مشاكل.
فقد الأداء الأقصى بسبب حقيقة أنه لم يعد بإمكاننا الرجوع إلى البيانات في عملية أو كائن آخر. يجب أن نتحرك ، لكن هذا ليس مجانيًا.
مقدار الحمل لنسخ البيانات بين العمليات. يمكننا أن نكتب برنامجًا في C يعمل على جميع المراكز الثمانين ويعالج صفيف بيانات واحدًا ، وسنفترض أنه يقوم بذلك بشكل صحيح وصحيح. في Erlang ، لا يمكنك القيام بذلك: تحتاج إلى قطع البيانات بعناية ، وتوزيعها على مجموعة من العمليات ، وتتبع كل شيء. هذا الاتصال يكلف الموارد - دورات المعالج.
ما مدى سرعة أو بطء؟ نكتب كود Erlang منذ 10 سنوات. المنافس الوحيد الذي نجا من هذه السنوات العشر هو مكتوب بلغة جافا. معه ، لدينا تكافؤ شبه كامل في الأداء: يقول شخص ما أننا أسوأ ، شخص ما هو عليه. لكن لديهم Java بكل مشاكله ، بدءًا من JIT.
نكتب برنامجًا يخدم عشرات الآلاف من المقابس ويضخ عشرات غيغابايت من البيانات من خلال نفسه. اتضح فجأة أنه في هذه الحالة ، تعد
صحة الخوارزميات والقدرة على تصحيح كل هذا في الإنتاج أكثر أهمية من الكعك المحتمل لجافا . لقد تم استثمار مليارات الدولارات فيها ، لكن هذا لا يمنح Java JIT أي مزايا سحرية.
ولكن إذا أردنا قياس معايير غبية وغير منطقية ، مثل "حساب أرقام فيبوناتشي" ، فمن المحتمل أن يكون إرلانج هنا أسوأ من بيثون أو ما يمكن مقارنته.
الحمل من تخصيص الرسالة. في بعض الأحيان يضر. على سبيل المثال ، لدينا بعض القطع في C في الشفرة ، وفي هذه الأماكن لم تنجح على الإطلاق مع Erlang. , , .
Erlang
, , . , ,
receive
send receive
. — , .
, , .
Python
. . , Python - .
,
. - Python, , 20 , 40.
,
. - , , Elixir, , .
Moscow Python Conf++ . , 6 4 . , , ) ) . Call for Papers 13 , 27 .