التزامن أسطورة

مرحبا بالجميع!

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

أولئك الذين يرغبون أو يفكرون في كتابة كتاب يتطرق إلى مثل هذه المواضيع - الكتابة بشكل شخصي.

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

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

الفرق بين التزامن وعدم التزامن في اللغة وإنشاء الأنظمة هو مجرد جانب من جوانب التصميم له أسس فيزيائية عميقة. يبدأ معظم المبرمجين على الفور العمل مع مثل هذه البرامج واللغات ، حيث يتم تضمين التنفيذ المتزامن. في الواقع ، هذا أمر طبيعي لدرجة أنه لا أحد يذكره أو يتحدث عنه مباشرة. يعني مصطلح "متزامن" في هذا السياق أن الحساب يتم على الفور ، مثل سلسلة من الخطوات المتتالية ، ولا يحدث أي شيء آخر قبل اكتماله. أقوم بتنفيذ “c = a + b” “x = f(y)” - ولن يحدث أي شيء آخر حتى يكتمل هذا التعليمات.

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

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

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

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

يتم استخدام آليات خطيرة لتحسين هذه الظواهر: خط الأنابيب مع عرض رائد لتدفق الأوامر ، وعمليات متعددة في وقت واحد لجلب من الذاكرة وتخزين البيانات الحالية ، والتنبؤ بالفروع ومحاولات تحسين البرنامج ، حتى عندما ينتقل إلى موقع ذاكرة آخر ، والتحكم الدقيق في حواجز الذاكرة لضمان أن كل هذه الآلية المعقدة ستستمر في توفير نموذج ذاكرة متسق لبيئة برمجة عالية المستوى. كل هذه الأشياء تتم في محاولة لتحسين الأداء والاستفادة القصوى من الأجهزة لإخفاء هذه التأخيرات من 10-100 نانوثانية في التسلسل الهرمي للذاكرة وتوفير نظام يفترض أن يتم فيه تنفيذ متزامن ، مع الاستمرار في الضغط على الأداء اللائق من قلب المعالج.

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

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

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

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

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

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

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

وبطبيعة الحال ، يمكن أيضًا تنفيذ التعبئة المحلية على الواجهة المتزامنة ، ولكن لهذا عليك إما "إخفاء الحقيقة" إلى حد كبير أو تجميع حزمة البرنامج كميزة خاصة للواجهة ، مما قد يعقد العميل بأكمله. يتم تخزين I / O مثال كلاسيكي لإخفاء الحقيقة. يستدعي التطبيق “write(byte)” ، وتعيد الواجهة success ، ولكن في الواقع ، فإن السجل نفسه (بالإضافة إلى معلومات حول ما إذا كان قد تم تمريره بنجاح) لن يتم حتى يتم تعبئة المخزن المؤقت أو إفراغه بشكل صريح ، ويحدث هذا عند إغلاق الملف . يمكن للعديد من التطبيقات تجاهل هذه التفاصيل - تحدث الفوضى فقط عندما يحتاج التطبيق إلى ضمان بعض تسلسل العمليات المتفاعل ، بالإضافة إلى فكرة حقيقية عما يحدث في المستويات الدنيا.

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

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

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

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

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

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

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

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

مواضيع أخرى

الإلغاء

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

اختناق / إدارة الموارد

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

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

لضمان الأداء التفاعلي الجيد ، يجب أن يوفر تطبيقنا بعض العرض (أي ، تأكد من تحديث الخط الذي يتم فيه تحديث المؤشر وتقديمه) ، ثم تحقق مما إذا كان هناك أي إدخال آخر للوحة المفاتيح يحتاج إلى القراءة. , ( , ), , . API. , , - . , . UI , .

, 30 (-, Facebook iPhone ). – ( , ), , . , , .



, . , Microsoft, , API – , , . , , – : «, !» , , .

, . – , . , : , , , . , - . , , async/await . «» , , , JavaScript. : , . Async/await , , . . , , , .

. , , . , , , . , , ( !). () , , .

, . , . async/await, , , , .

, , , – . , . – , , , ( , – Word Excel). , , - , , , .
, , , , .
, – . .

الاستنتاجات

. – , , . , , , . , ; , .

Source: https://habr.com/ru/post/ar423573/


All Articles