قائمة طعام
مجاني
التسجيل
الصفحة الرئيسية  /  الوسائط المتعددة/ برامج متعددة مؤشرات الترابط مع أمثلة. ثمانية قواعد بسيطة لتطوير التطبيقات متعددة مؤشرات الترابط

برامج متعددة مؤشرات الترابط مع أمثلة. ثمانية قواعد بسيطة لتطوير التطبيقات متعددة مؤشرات الترابط

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

إذن ما هي هذه القواعد

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

مبرمج آخر ، يواجه مشكلة ، يفكر: "حسنًا ، سأستخدم التدفقات هنا." والآن لديه عشر مشاكل - بيل شندلر.

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

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

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

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

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

لا يوجد شيء صعب في تحسين متوسط ​​البرنامج أحادي الخيوط وتحويله إلى برنامج متعدد الخيوط. على الأقل لا ينبغي أن يكون. تنشأ الصعوبات لسببين:

  • لا يعرف المبرمجون كيفية تطبيق طرق تطوير بسيطة ومعروفة ؛
  • معظم المعلومات الواردة في الكتب حول البرمجة متعددة الخيوط صحيحة من الناحية الفنية ، ولكنها غير قابلة للتطبيق تمامًا لحل المشكلات التطبيقية.

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

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

في برنامج تسلسلي واحد مترابط ، يتناسب احتمال تلف البيانات بشكل مباشر مع عدد المكونات التي يمكنها تغيير البيانات.

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

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

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

يعد البرنامج متعدد مؤشرات الترابط مع حالة شاملة قابلة للتغيير على مستوى العالم أحد أكثر الأمثلة بلاغةً على مبدأ عدم اليقين في Heisenberg الذي أعرفه. من المستحيل التحقق من حالة البرنامج دون تغيير سلوكه.

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

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

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

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

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

يمكن التعليق على هذا البيان بطريقتين:

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

كلا من هذه التفسيرات صحيحة.

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

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

معظم ما تعرفه عن تعدد مؤشرات الترابط غير ذي صلة

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

تخيل إخبار مراهق بكيفية تجميع محرك احتراق داخلي بنفسه. ثم ، بدون أي تدريب على القيادة ، تضعه خلف مقود السيارة وتقول ، "انطلق!" يفهم المراهق كيف تعمل السيارة ، لكن ليس لديه فكرة عن كيفية الانتقال من النقطة أ إلى النقطة ب عليها.

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

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

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

  • يقرأ تدفق الإدخال البيانات ويضعها في قائمة انتظار الإدخال ؛
  • يقرأ مؤشر ترابط العامل السجلات من قائمة انتظار الإدخال ويعالجها ويضع النتائج في قائمة انتظار الإخراج ؛
  • يقرأ دفق الإخراج الإدخالات من قائمة انتظار الإخراج ويخزنها.

تعمل هذه الخيوط الثلاثة بشكل مستقل ، ويتم الاتصال بينها على مستوى قائمة الانتظار.

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

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

والأهم من ذلك ، أن لغات البرمجة والمكتبات الحديثة تجعل من السهل جدًا إنشاء تطبيقات المنتج والمستهلك. في .NET ، ستجد المجموعات المتوازية ومكتبة TPL Dataflow. تحتوي Java على خدمة Executor بالإضافة إلى BlockingQueue وفئات أخرى من مساحة الاسم java.util.concurrent. يحتوي C ++ على مكتبة Boost threading ومكتبة Thread Building Blocks من Intel. يقدم Microsoft Visual Studio 2013 عوامل غير متزامنة. توجد مكتبات مماثلة في Python و JavaScript و Ruby و PHP وبقدر ما أعرف في العديد من اللغات الأخرى. يمكنك إنشاء تطبيق منتج-مستهلك باستخدام أي من هذه الحزم ، دون الحاجة إلى اللجوء إلى الأقفال أو الإشارات أو متغيرات الحالة أو أي عناصر أولية أخرى للمزامنة.

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

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

العمل مع المكتبات

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

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

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

نهاية الملف. بهذه الطريقة ، لا يتم خلط إدخالات السجل التي يتم إجراؤها بواسطة عمليات مختلفة أبدًا. توفر أنظمة Unix الأكثر حداثة خدمة سجل نظام خاصة (3C) للتسجيل.

مزايا:

  1. سهولة التطوير. في الواقع ، نقوم بتشغيل العديد من النسخ من تطبيق مترابط واحد ويتم تشغيلها بشكل مستقل عن بعضها البعض. من الممكن عدم استخدام أي API و وسائل الاتصال بين العمليات.
  2. موثوقية عالية. لا يؤثر الإنهاء غير الطبيعي لأي من العمليات على باقي العمليات بأي شكل من الأشكال.
  3. قابلية جيدة. سيعمل التطبيق على أي نظام تشغيل متعدد المهام
  4. حماية عالية. يمكن تشغيل عمليات التطبيق المختلفة نيابة عن مستخدمين مختلفين. وبالتالي ، يمكنك تنفيذ مبدأ الامتياز الأقل ، عندما يكون لكل عملية الحقوق الضرورية له للعمل فقط. حتى إذا تم العثور على خطأ في بعض العمليات التي تسمح بتنفيذ التعليمات البرمجية عن بُعد ، فلن يتمكن المهاجم إلا من الحصول على مستوى الوصول الذي تم من خلاله تنفيذ هذه العملية.

سلبيات:

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

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

أمثلة: apache 1.x (خادم HTTP)

تطبيقات المعالجة المتعددة الاتصال عبر مآخذ التوصيل والأنابيب وقوائم انتظار رسائل نظام V IPC

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

مزايا:

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

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

سلبيات:

  1. هذه البنية ليست سهلة التصميم والتنفيذ لجميع التطبيقات.
  2. تفترض جميع أنواع أدوات IPC المدرجة نقل البيانات التسلسلية. إذا كان الوصول العشوائي إلى البيانات المشتركة مطلوبًا ، فإن هذه البنية غير ملائمة.
  3. يتطلب نقل البيانات عبر أنبوب ومقبس وقائمة انتظار رسائل تنفيذ مكالمات النظام ونسخ البيانات مرتين - أولاً من مساحة عنوان العملية الأصلية إلى مساحة عنوان kernel ، ثم من مساحة عنوان kernel إلى الذاكرة عملية الهدف... هذه عمليات مكلفة. عند نقل كميات كبيرة من البيانات ، يمكن أن تصبح هذه مشكلة خطيرة.
  4. معظم الأنظمة لها حدود على العدد الإجمالي للأنابيب والمآخذ ومنشآت IPC. على سبيل المثال ، يسمح Solaris بحد أقصى 1،024 أنبوبًا مفتوحًا ومآخذ توصيل وملفات لكل عملية بشكل افتراضي (بسبب قيود استدعاء النظام المحدد). الحد المعماري لـ Solaris هو 65.536 أنبوبًا ومآخذًا وملفات لكل عملية.

    لا يزيد الحد الأقصى لعدد مآخذ TCP / IP عن 65536 لكل واجهة شبكة (بسبب تنسيق رؤوس TCP). توجد قوائم انتظار رسائل System V IPC في مساحة عنوان kernel ، لذلك توجد حدود صارمة على عدد قوائم الانتظار في النظام وعلى كمية وعدد الرسائل الموجودة في قائمة الانتظار في نفس الوقت.

  5. يعد إنشاء العملية وإتلافها والتبديل بين العمليات عمليات مكلفة. هذه البنية ليست مثالية في جميع الحالات.

تطبيقات الذاكرة المشتركة متعددة المعالجات

يمكن أن تكون الذاكرة المشتركة هي الذاكرة المشتركة System V IPC وتعيين ملف إلى ذاكرة. لمزامنة الوصول ، يمكنك استخدام إشارات System V IPC و mutexes و POSIX semaphores ، وعند تعيين الملفات إلى الذاكرة ، يمكنك التقاط مقاطع من الملف.

مزايا:

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

سلبيات:

  1. التعقيد النسبي للتطوير. من الصعب جدًا اكتشاف أخطاء مزامنة الوصول - ما يسمى بأخطاء التنازع - عند الاختبار.

    يمكن أن يؤدي هذا إلى زيادة من 3 إلى 5 أضعاف في إجمالي تكلفة التطوير مقارنة بالبنى متعددة المهام ذات الخيوط المفردة أو الأبسط.

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

    يؤدي هذا غالبًا إلى تعطل بقية التطبيق. تقوم بعض التطبيقات ، مثل Lotus Domino ، بإيقاف العمليات على مستوى الخادم عمدًا عند إنهاء أي منها بشكل غير طبيعي.

  3. يعد إنشاء العملية وإتلافها والتبديل بينها عمليات مكلفة.

    لذلك ، هذه البنية ليست مثالية لجميع التطبيقات.

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

    أي ، في أسوأ السيناريوهات ، يمكن للمهاجم الحصول على مستوى الوصول المقابل لأعلى مستويات الوصول لعمليات التطبيق.

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

في الواقع ، تجمع هذه البنية بين عيوب المعالجة المتعددة والتطبيقات متعددة مؤشرات الترابط المناسبة. ومع ذلك ، فإن عددًا من التطبيقات الشائعة التي تم تطويرها في الثمانينيات وأوائل التسعينيات ، قبل واجهات برمجة التطبيقات متعددة مؤشرات الترابط الموحدة في يونكس ، تستخدم هذه البنية. هناك العديد من خوادم قواعد البيانات ، التجارية (Oracle و DB2 و Lotus Domino) ، بالإضافة إلى البرامج المجانية والإصدارات الحديثة من Sendmail وبعض خوادم البريد الأخرى.

تطبيقات متعددة الخيوط المناسبة

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

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

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

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

    الفصل 10.

    تطبيقات متعددة الخيوط

    يعتبر تعدد المهام في أنظمة التشغيل الحديثة أمرًا مفروغًا منه [ قبل Apple OS X ، لم يكن لدى أجهزة كمبيوتر Macintosh أنظمة تشغيل حديثة متعددة المهام. من الصعب جدًا تصميم نظام تشغيل مع مهام متعددة كاملة بشكل صحيح ، لذلك يجب أن يعتمد OS X على نظام Unix.]. يتوقع المستخدم أنه عند تشغيل محرر النصوص وعميل البريد في نفس الوقت ، لن تتعارض هذه البرامج ، وعند تلقي البريد الإلكتروني ، لن يتوقف المحرر عن العمل. عند إطلاق عدة برامج في نفس الوقت ، ينتقل نظام التشغيل بسرعة بين البرامج ، ويزودها بمعالج بدوره (ما لم يتم ، بالطبع ، تثبيت عدة معالجات على الكمبيوتر). نتيجة ل، وهمتشغيل برامج متعددة في نفس الوقت ، لأنه حتى أفضل طابع (وأسرع اتصال بالإنترنت) لا يمكنه مواكبة المعالج الحديث.

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

    لم يكن لدى VB دعم متعدد مؤشرات الترابط العادي. صحيح ، ظهر أحد أصنافه في VB5 - نموذج التدفق التعاوني(خيوط الشقة). كما سترى قريبًا ، يوفر النموذج التعاوني للمبرمج بعض مزايا تعدد مؤشرات الترابط ، لكنه لا يستفيد استفادة كاملة من جميع الميزات. عاجلاً أم آجلاً ، عليك الانتقال من آلة تدريب إلى آلة حقيقية ، وأصبح VB .NET هو الإصدار الأول من VB مع دعم نموذج مجاني متعدد مؤشرات الترابط.

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

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

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

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

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

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

    إدخال multithreading

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

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

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

    قبل الانتقال إلى آليات البرامج متعددة مؤشرات الترابط ، من الضروري الإشارة إلى أحد الظروف التي غالبًا ما تسبب ارتباكًا بين المبتدئين في مجال البرمجة متعددة مؤشرات الترابط.

    سيتم تنفيذ إجراء ، وليس كائن ، في تدفق البرنامج.

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

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

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

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

    تتركز ميزات .NET الأساسية المتعلقة باستخدام مؤشرات الترابط البرمجية في مساحة اسم Threading. لذلك ، يجب أن تبدأ معظم البرامج متعددة مؤشرات الترابط بالسطر التالي:

    نظام الواردات

    يؤدي استيراد مساحة اسم إلى تسهيل كتابة البرنامج وتمكين تقنية IntelliSense.

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

    الموضوع الفرعي للمندوب العام

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

    مجالات التطبيق

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

    نظام الواردات

    وحدة نمطية

    الرئيسية من الباطن ()

    تعتيم النطاق باسم AppDomain

    theDomain = AppDomain.CurrentDomain

    تجميعات قاتمة ()

    التجميعات = theDomain.GetAssemblies

    خافت وجمعية xAs

    لكل جمعية في الجمعيات

    Console.WriteLinetanAssembly.Full الاسم) التالي

    وحدة التحكم.

    End Sub

    وحدة النهاية

    خلق تيارات

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

    الطبقة العامة WillUseThreads

    طرح عام من عداد ()

    العد الخافت كعدد صحيح

    هل بينما العدد الصحيح - = 1

    Console.WriteLlne ("أنا في مؤشر ترابط آخر وعداد ="

    & عدد)

    حلقة

    End Sub

    فئة النهاية

    نظرًا لأن شرط Do loop يكون دائمًا صحيحًا ، فقد تعتقد أنه لن يتداخل أي شيء مع تنفيذ إجراء SubtractFromCounter. ومع ذلك ، في تطبيق متعدد مؤشرات الترابط ، هذا ليس هو الحال دائمًا.

    يحتوي المقتطف التالي على الإجراء Sub Main الذي يبدأ مؤشر الترابط والأمر Imports:

    خيار صارم على نظام الواردات

    الرئيسية من الباطن ()

    1 خافت myTest كإرادة جديدة ()

    2 Dim bThreadStart as New ThreadStart (AddressOf _

    myTest.SubtractFromCounter)

    3 خافت خيط خيط جديد (bThreadStart)

    4 بوصات bThread.Start ()

    خافت أنا كعدد صحيح

    5 افعل بينما صحيح

    Console.WriteLine ("في الخيط الرئيسي والعدد هو" & i) i + = 1

    حلقة

    End Sub

    وحدة النهاية

    دعنا نلقي نظرة على أهم النقاط في التسلسل. بادئ ذي بدء ، يعمل إجراء Sub Man n دائمًا في التيار(موضوع الرئيسي). في برامج .NET ، يوجد دائمًا ما لا يقل عن خيطين قيد التشغيل: الخيط الرئيسي وخيط تجميع البيانات المهملة. ينشئ السطر 1 مثيلًا جديدًا لفئة الاختبار. في السطر 2 ، قمنا بإنشاء مفوض ThreadStart وتمرير عنوان إجراء SubtractFromCounter لمثيل فئة الاختبار الذي تم إنشاؤه في السطر 1 (يسمى هذا الإجراء بدون معلمات). حسنمن خلال استيراد مساحة اسم مؤشر الترابط ، يمكن حذف الاسم الطويل. يتم إنشاء كائن مؤشر الترابط الجديد في السطر 3. لاحظ تمرير المفوض ThreadStart عند استدعاء مُنشئ فئة مؤشر الترابط. يفضل بعض المبرمجين ربط هذين السطرين في سطر منطقي واحد:

    Dim bThread as New Thread (New ThreadStarttAddressOf _)

    myTest.SubtractFromCounter))

    أخيرًا ، السطر 4 "يبدأ" مؤشر الترابط عن طريق استدعاء أسلوب البدء لمثيل مؤشر الترابط الذي تم إنشاؤه لمفوض ThreadStart. من خلال استدعاء هذه الطريقة ، نخبر نظام التشغيل أنه يجب تشغيل إجراء Subtract في مؤشر ترابط منفصل.

    الكلمة "يبدأ" في الفقرة السابقة محاطة بعلامات اقتباس ، لأن هذه واحدة من العديد من الشذوذ في البرمجة متعددة مؤشرات الترابط: استدعاء Start لا يؤدي فعليًا إلى بدء الخيط! إنه ببساطة يخبر نظام التشغيل بجدولة سلسلة الرسائل المحددة للتشغيل ، ولكن البدء مباشرة هو خارج سيطرة البرنامج. لن تتمكن من بدء تنفيذ سلاسل الرسائل بمفردك ، لأن نظام التشغيل يتحكم دائمًا في تنفيذ سلاسل الرسائل. في قسم لاحق ، ستتعلم كيفية استخدام الأولوية لجعل نظام التشغيل يبدأ مؤشر الترابط بشكل أسرع.

    في التين. يوضح الإصدار 10.1 مثالاً لما يمكن أن يحدث بعد بدء البرنامج ثم مقاطعته باستخدام مفتاح Ctrl + Break. في حالتنا ، بدأ الخيط الجديد فقط بعد زيادة العداد في الخيط الرئيسي إلى 341!

    أرز. 10.1. وقت تشغيل برنامج بسيط متعدد مؤشرات الترابط

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

    أرز. 10.2. التبديل بين المواضيع في برنامج بسيط متعدد مؤشرات الترابط

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

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

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

    bThread.Priority = أولوية الموضوع

    أرز. 10.3. عادة ما يبدأ الموضوع ذو الأولوية القصوى بشكل أسرع

    أرز. 10.4. يتم توفير المعالج أيضًا للخيوط ذات الأولوية المنخفضة

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

    النوع الذي تم تعداده ThreadPrlority يتضمن قيمًا لخمسة مستويات ذات أولوية:

    أولوية الموضوع أعلى

    أولوية الموضوع فوق عادي

    الخيط: عادي

    أولوية الموضوع أدناه عادي

    أولوية الموضوع

    طريقة الانضمام

    في بعض الأحيان ، يحتاج مؤشر ترابط البرنامج إلى الإيقاف المؤقت حتى ينتهي مؤشر ترابط آخر. لنفترض أنك تريد إيقاف الخيط 1 مؤقتًا حتى يكمل الخيط 2 حسابه. من أجل هذا من تيار 1يتم استدعاء طريقة الانضمام للدفق 2. بمعنى آخر ، الأمر

    Thread2.Join ()

    يعلق الموضوع الحالي وينتظر حتى يكتمل الموضوع 2. ينتقل الموضوع 1 إلى دولة مقفلة.

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

    thread2.Join (عدد صحيح)

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

    تذكر القاعدة الأساسية: سواء أكتمل الخيط 2 أو انتهى ، لا يمكنك التحكم في وقت تنشيط الخيط 1.

    أسماء الخيط ، CurrentThread و ThreadState

    تقوم الخاصية Thread.CurrentThread بإرجاع مرجع إلى كائن مؤشر الترابط الذي يتم تنفيذه حاليًا.

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

    MsgBox (Thread.CurrentThread.Name)

    غالبًا ما يتضح أن الكود يتم تنفيذه في سلسلة مختلفة تمامًا كان من المفترض أن يتم تنفيذها منه.

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

    نافذة تيارات

    نافذة الخيوط في Visual Studio .NET لا تقدر بثمن في تصحيح أخطاء البرامج متعددة مؤشرات الترابط. يتم تنشيطه بواسطة Debug> أمر قائمة Windows الفرعية في وضع المقاطعة. لنفترض أنك قمت بتعيين اسم لسلسلة bThread بالأمر التالي:

    bThread.Name = "طرح مؤشر ترابط"

    يظهر في الشكل عرض تقريبي لنافذة التدفقات بعد مقاطعة البرنامج باستخدام تركيبة المفاتيح Ctrl + Break (أو بطريقة أخرى). 10.5.

    أرز. 10.5. نافذة تيارات

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

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

    تعليق دفق

    يمكن نقل التدفقات غير المستخدمة مؤقتًا إلى حالة سلبية باستخدام طريقة Slеer. يعتبر الدفق السلبي محظورًا أيضًا. بالطبع ، عندما يتم وضع مؤشر ترابط في حالة سلبية ، ستحتوي بقية الخيوط على المزيد من موارد المعالج. الصيغة القياسية لطريقة Slеer هي كما يلي: Thread.Sleep (periodal_in_milliseconds)

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

    إصدار آخر من وضع السكون يجعل الخيط الحالي يتخلى عن بقية الوقت المخصص لوحدة المعالجة المركزية:

    الموضوع.النوم (0)

    الخيار التالي يضع الخيط الحالي في حالة سلبية لفترة غير محدودة (يحدث التنشيط فقط عند استدعاء المقاطعة):

    الموضوع. slеer (مهله. لانهائية)

    نظرًا لأنه يمكن مقاطعة الخيوط المنفعلة (حتى مع مهلة غير محدودة) من خلال طريقة المقاطعة ، مما يؤدي إلى بدء ThreadlnterruptExcepti عند الاستثناء ، يتم دائمًا تضمين استدعاء Slayer في كتلة Try-Catch ، كما في المقتطف التالي:

    محاولة

    خيط.نوم (200)

    "تمت مقاطعة الحالة السلبية لمؤشر الترابط

    Catch e كاستثناء

    "استثناءات أخرى

    حاول إنهاء

    يتم تشغيل كل برنامج .NET على مؤشر ترابط برنامج ، لذلك يتم استخدام طريقة السكون أيضًا لتعليق البرامج (إذا لم يتم استيراد مساحة اسم Threadipg بواسطة البرنامج ، يجب استخدام الاسم المؤهل Threading.Thread. Sleep).

    إنهاء أو مقاطعة سلاسل البرنامج

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

    طريقة الخيوط الفرعية ()

    "يجب أن يوفر البرنامج وسائل للمسح

    "متغير شرطي.

    "على سبيل المثال ، يمكن تصميم المتغير الشرطي كخاصية

    افعل while conditionVariable = False And MoreWorkToDo

    "الكود الرئيسي

    حلقة نهاية فرعية

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

    إذا كان يجب التحقق من متغير الشرط في موقع معين ، فاستخدم الأمر If-Then جنبًا إلى جنب مع Exit Sub داخل حلقة لا نهائية.

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

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

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

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

    تعليق وقتل الخيوط

    تحتوي مساحة اسم مؤشر الترابط على طرق أخرى تقاطع الترابط العادي:

    • تعليق؛
    • إحباط.

    من الصعب تحديد سبب تضمين .NET دعمًا لهذه الأساليب - عند استدعاء Suspend and Abort ، من المرجح أن يصبح البرنامج غير مستقر. لا تسمح أي من الطرق بإلغاء التهيئة الطبيعي للتيار. أيضًا ، عند استدعاء Suspend أو Abort ، لا يمكنك التنبؤ بالحالة التي سيترك بها مؤشر الترابط الكائنات بعد تعليقها أو إحباطها.

    يؤدي استدعاء Abort إلى طرح ThreadAbortException. لمساعدتك في فهم سبب عدم معالجة هذا الاستثناء الغريب في البرامج ، إليك مقتطف من وثائق .NET SDK:

    “... عندما يتم إتلاف مؤشر ترابط عن طريق استدعاء Abort ، فإن وقت التشغيل يطرح ThreadAbortException. هذا نوع خاص من الاستثناءات لا يمكن للبرنامج اكتشافه. عند طرح هذا الاستثناء ، يقوم وقت التشغيل بتشغيل كافة كتل أخيرًا قبل إنهاء مؤشر الترابط. نظرًا لأن أي إجراء يمكن أن يحدث في عمليات الحظر أخيرًا ، فاتصل بـ "انضمام" للتأكد من تدمير الدفق. "

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

    خيوط الخلفية (شياطين)

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

    إذا لم يتبق سوى سلاسل رسائل في الخلفية في التطبيق ، فسيتم إنهاء التطبيق تلقائيًا.

    مثال أكبر: استخراج البيانات من كود HTML

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

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

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

    البحث الفرعي العام ()

    m_Rank = ScrapeAmazon ()

    Console.WriteLine ("رتبة" & m_Name & "Is" & GetRank)

    End Sub

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

    Dim theBook (3.1) As String theBook (0.0) = "1893115992"

    theBook (0.l) = "برمجة VB .NET" "إلخ.

    يتم إنشاء أربعة تدفقات في نفس الحلقة التي يتم فيها إنشاء كائنات AmazonRanker:

    بالنسبة إلى i = 0 إلى 3

    محاولة

    theRanker = New AmazonRanker (الكتاب (i.0). theBookd.1))

    aThreadStart = New ThreadStar (AddressOf theRanker.FindRan ()

    aThread = موضوع جديد (aThreadStart)

    aThread.Name = الكتاب (على سبيل المثال)

    aThread.Start () القبض على كاستثناء

    Console.WriteLine (الرسالة الإلكترونية)

    حاول إنهاء

    التالي

    فيما يلي النص الكامل للبرنامج:

    تقييد الخيار على الواردات System.IO Imports System.Net

    نظام الواردات

    وحدة نمطية

    الرئيسية من الباطن ()

    تعتيم الكتاب (3.1) كسلسلة

    theBook (0.0) = "1893115992"

    theBook (0.l) = "برمجة VB .NET"

    theBook (l.0) = "1893115291"

    theBook (l.l) = "برمجة قاعدة البيانات VB .NET"

    الكتاب (2،0) = "1893115623"

    theBook (2.1) = مقدمة "المبرمج" إلى C #. "

    theBook (3.0) = "1893115593"

    theBook (3.1) = "سد النظام الأساسي الصافي"

    خافت أنا كعدد صحيح

    خافت theRanker As = AmazonRanker

    قم بتعتيم الخيط وابدأ باسم خيوط المعالجة

    خافت الخيط على أنه خيوط

    بالنسبة إلى i = 0 إلى 3

    محاولة

    theRanker = New AmazonRankerttheBook (i.0). الكتاب (i.1))

    aThreadStart = New ThreadStart (AddressOf theRanker. FindRank)

    aThread = موضوع جديد (aThreadStart)

    aThread.Name = الكتاب (على سبيل المثال)

    aThread.Start ()

    Catch e كاستثناء

    Console.WriteLlnete.Message)

    إنهاء حاول التالي

    وحدة التحكم.

    End Sub

    وحدة النهاية

    فئة عامة AmazonRanker

    m_URL الخاص كسلسلة

    m_Rank الخاص باعتباره عددًا صحيحًا

    m_Name الخاص كسلسلة

    عام Sub جديد (ByVal ISBN كسلسلة. ByVal theName كسلسلة)

    m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

    m_Name = الاسم End Sub

    البحث الفرعي العام () m_Rank = ScrapeAmazon ()

    Console.Writeline ("رتبة" & m_Name & "is"

    & GetRank) End Sub

    ملكية عامة للقراءة فقط GetRank () مثل String Get

    إذا m_Rank<>0 ثم

    إرجاع CStr (m_Rank) عدا ذلك

    " مشاكل

    إنهاء إذا

    إنهاء Get

    نهاية الممتلكات

    الخاصية العامة للقراءة فقط GetName () مثل الحصول على سلسلة

    إرجاع m_Name

    إنهاء Get

    نهاية الممتلكات

    الوظيفة الخاصة ScrapeAmazon () كما حاول بشكل صحيح

    تعتيم عنوان URL باعتباره Uri جديد (m_URL)

    تعتيم الطلب باسم WebRequest

    theRequest = WebRequest.Create (عنوان URL)

    تعتيم الاستجابة باعتباره استجابة ويب

    theResponse = theRequest.GetResponse

    تعتيم القارئ باعتباره StreamReader الجديد (theResponse.GetResponseStream ())

    تعتيم البيانات كسلسلة

    theData = aReader.ReadToEnd

    تحليل العودة (البيانات)

    قبض E كاستثناء

    Console.WriteLine (E.Message)

    Console.WriteLine (E.StackTrace)

    وحدة التحكم. قراءة الخط ()

    وظيفة End Try End

    تحليل الوظيفة الخاصة (ByVal theData As String) كعدد صحيح

    خافت الموقع As.Integer Location = theData.IndexOf (" أمازون.كوم

    ترتيب المبيعات:") _

    + "ترتيب مبيعات Amazon.com:".طول

    خافت الحرارة كسلسلة

    افعل حتى theData.Substring (Location.l) = "<" temp = temp

    & theData.Substring (Location.l) الموقع + = 1 حلقة

    عودة كلنت (درجة الحرارة)

    وظيفة النهاية

    فئة النهاية

    تُستخدم العمليات متعددة مؤشرات الترابط بشكل شائع في مساحات الأسماء .NET و I / O ، لذلك توفر مكتبة .NET Framework طرقًا خاصة غير متزامنة لها. لمزيد من المعلومات حول استخدام الأساليب غير المتزامنة عند كتابة برامج متعددة مؤشرات الترابط ، راجع أساليب BeginGetResponse و EndGetResponse لفئة HTTPWebRequest.

    الخطر الرئيسي (بيانات عامة)

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

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

    إذا كان mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

    خيط.نوم (200)

    صيد التعادل باسم ThreadlnterruptException

    "تم مقاطعة الانتظار السلبي

    Catch e كاستثناء

    "استثناءات تجربة النهاية الأخرى

    mHouse.HouseTemp + - 5 "إلخ.

    يوجد أدناه رمز المصدر الكامل للبرنامج. النتيجة موضحة في الشكل. 10.6: وصلت درجة الحرارة في المنزل إلى 105 درجة فهرنهايت (40.5 درجة مئوية)!

    1 خيار صارم

    2 نظام الواردات

    3 وحدة نمطية

    4 فرعي ()

    5 خافت myHouse كمنزل جديد (l0)

    6 وحدة تحكم. قراءة الخط ()

    7 نهاية الفرعية

    8 الوحدة النمطية النهائية

    9 بابك كلاس هاوس

    10 رقم عام MAX_TEMP كعدد صحيح = 75

    11 mCurTemp الخاص كعدد صحيح = 55

    12 غرفة خاصة () كغرفة

    13 عام فرعي جديد (ByVal numOfRooms As Integer)

    14 ReDim mRooms (numOfRooms = 1)

    15 خافت كعدد صحيح

    16 خافت في الخيط - ابدأ على شكل خيوط

    17 خافت الخيط كخيط

    18 بالنسبة إلى i = 0 إلى عدد الغرف -1

    19 جرب

    20 mRooms (i) = NewRoom (Me، mCurTemp، CStr (i) & "throom")

    21 aThreadStart - New ThreadStart (AddressOf _

    mRooms (i) .CheckTempInRoom)

    22 aThread = موضوع جديد (aThreadStart)

    23 أ - الخيط - ابدأ ()

    24 Catch E كاستثناء

    25 Console.WriteLine (E.StackTrace)

    26 نهاية المحاولة

    27 التالي

    28 نهاية الفرعية

    29 الملكية العامة HouseTemp () كعدد صحيح

    ثلاثين. احصل على

    31 إرجاع mCurTemp

    32 End Get

    33 مجموعة (قيمة ByVal كعدد صحيح)

    34 mCurTemp = القيمة 35 مجموعة النهاية

    36 نهاية الملكية

    37 نهاية الفصل

    38 غرفة الصف العامة

    39 خاص mCurTemp باعتباره عددًا صحيحًا

    40 خاص mName كسلسلة

    41 منزل خاص في المنزل

    42 Public Sub New (ByVal theHouse As House،

    ByVal temp As Integer، ByVal roomName كسلسلة)

    43 م المنزل = البيت

    44 mCurTemp = درجة الحرارة

    45 mName = اسم الغرفة

    46 End Sub

    47 فحص فرعي عام

    48 درجة حرارة التغيير ()

    49 End Sub

    50 درجة حرارة فرعية خاصة ()

    51 حاول

    52 إذا كان mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

    53 خيط النوم (200)

    54 متر منزل.بيت تيمب + - 5

    55 Console.WriteLine ("أنا في" & Me.mName & _

    56 ". درجة الحرارة الحالية" & mHouse.HouseTemp)

    57. Elself mHouse.HouseTemp. البيت< mHouse.MAX_TEMP Then

    58 خيط النوم (200)

    59 mHouse.HouseTemp + = 1

    60 Console.WriteLine ("أنا في" & Me.mName & _

    61 ". درجة الحرارة الحالية" & mHouse.HouseTemp)

    62 آخر

    63 Console.WriteLine ("أنا في" & Me.mName & _

    64 ". درجة الحرارة الحالية" & mHouse.HouseTemp)

    65 "لا تفعل شيئًا ، درجة الحرارة طبيعية

    66 نهاية إذا

    67 قبض على موضوع ThreadlnterruptException

    68 "تم مقاطعة الانتظار السلبي

    69 قبض على كاستثناء

    70 "استثناءات أخرى

    71 نهاية المحاولة

    72 End Sub

    73 نهاية الفئة

    أرز. 10.6. قضايا تعدد

    الإجراء الفرعي الرئيسي (الخطوط 4-7) ينشئ "منزلًا" به عشر "غرف". يحدد فصل البيت درجة حرارة قصوى تبلغ 75 درجة فهرنهايت (حوالي 24 درجة مئوية). تحدد السطور 13-28 منشئ منزل معقدًا نوعًا ما. مفتاح فهم البرنامج هو الأسطر 18-27. ينشئ السطر 20 كائنًا آخر للغرفة ، ويتم تمرير مرجع إلى كائن المنزل إلى المُنشئ بحيث يمكن لكائن الغرفة الرجوع إليه إذا لزم الأمر. تبدأ الخطوط 21-23 عشر تدفقات لضبط درجة الحرارة في كل غرفة. تم تحديد فئة الغرفة في السطور 38-73. مرجع البيت coxpaيتم تخزينه في متغير mHouse في مُنشئ فئة الغرفة (السطر 43). يبدو رمز فحص درجة الحرارة وضبطها (الأسطر 50-66) بسيطًا وطبيعيًا ، ولكن كما سترى قريبًا ، فإن هذا الانطباع خادع! لاحظ أن هذا الرمز ملفوف في كتلة Try-Catch لأن البرنامج يستخدم طريقة السكون.

    لا يكاد أي شخص يوافق على العيش في درجات حرارة 105 درجة فهرنهايت (40.5 إلى 24 درجة مئوية). ماذا حدث؟ المشكلة تتعلق بالسطر التالي:

    إذا كان mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

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

    حل المشكلة: التزامن

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

    إذا كان mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

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

    درجة حرارة التغيير الفرعية الخاصة () SyncLock (mHouse)

    محاولة

    إذا كان mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

    خيط.نوم (200)

    mHouse.HouseTemp + = 5

    Console.WriteLine ("أنا في" & Me.mName & _

    ". درجة الحرارة الحالية" & mHouse.HouseTemp)

    Elself

    mHouse.HouseTemp< mHouse. MAX_TEMP Then

    الخيط.النوم (200) mHouse.HouseTemp + = 1

    Console.WriteLine ("Am in" & Me.mName & _ ". درجة الحرارة الحالية هي" & mHouse.HomeTemp) آخر

    Console.WriteLineC "أنا في" & Me.mName & _ ". درجة الحرارة الحالية هي" & mHouse.HouseTemp)

    "لا تفعل شيئًا ، درجة الحرارة طبيعية

    End If Catch التعادل باسم ThreadlnterruptException

    "تمت مقاطعة الانتظار الخامل بواسطة Catch e As Exception

    "استثناءات أخرى

    حاول إنهاء

    إنهاء SyncLock

    End Sub

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

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

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

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

    حالة الطبقة العامةمتغير

    الخزانة المشتركة الخاصة ككائن = كائن جديد ()

    خاص mOK As Boolean Shared

    الخاصية TheConditionVariable () As Boolean

    احصل على

    عودة mOK

    إنهاء Get

    تعيين (ByVal Value As Boolean) SyncLock (الخزانة)

    mOK = القيمة

    إنهاء SyncLock

    مجموعة النهاية

    نهاية الممتلكات

    فئة النهاية

    أمر SyncLock وفئة مراقب

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

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

    لحماية البيانات المشتركة في تطبيق متعدد مؤشرات الترابط ، يجب أن يقوم الأمر SyncLock بمزامنة كائن واحد في كل مرة.

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

    يمكن اعتبار وظيفة الأمر SyncLock على أنها مجموعة فرعية من وظائف فئة Monitor. فئة الشاشة قابلة للتخصيص بدرجة كبيرة ويمكن استخدامها لحل مهام المزامنة غير التافهة. يعد أمر SyncLock تناظريًا وثيقًا لطريقتي Enter و Exi t لفئة Moni tor:

    محاولة

    Monitor.Enter (theObject) أخيرًا

    Monitor.Exit (theObject)

    حاول إنهاء

    بالنسبة لبعض العمليات القياسية (زيادة / تقليل متغير ، تبادل محتويات متغيرين) ، يوفر .NET Framework فئة Interlocked ، التي تؤدي أساليبها هذه العمليات على المستوى الذري. باستخدام فئة Interlocked ، تكون هذه العمليات أسرع بكثير من استخدام الأمر SyncLock.

    المتشابكة

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

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

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

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

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

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

    1 خيار صارم

    2 نظام الواردات

    3 وحدة نمطية

    4 فرعي ()

    5 ديم توم كمبرمج جديد ("توم")

    6 ديم بوب كمبرمج جديد ("بوب")

    7 خافت في الخيط - ابدأ كخيط جديد ابدأ (العنوان من Tom.Eat)

    8 خافت الخيط كخيط جديد (aThreadStart)

    9 aThread.Name = "توم"

    10 خافت للخيط ابدأ كموضوع جديد

    11 خافت خيط خيط جديد (bThreadStart)

    12 bThread.Name = "بوب"

    13 أ - الخيط ابدأ ()

    14 ب - الخيط. ابدأ ()

    15 نهاية الفرعية

    16 نهاية الوحدة النمطية

    17 شوكة عامة

    18 mForkAvaiTable الخاص المشترك كـ Boolean = True

    19 مالك مشترك خاص كسلسلة = "لا أحد"

    20 ملكية خاصة للقراءة فقط OwnsUtensil () كسلسلة

    21 احصل

    22 عودة المالك

    23 End Get

    24 نهاية الملكية

    25 Public Sub GrabForktByVal كمبرمج)

    26 Console.Writel_ine (Thread.CurrentThread.Name & _

    "محاولة انتزاع الشوكة.")

    27 Console.WriteLine (Me.OwnsUtensil & "has the fork."). ...

    28 Monitor.Enter (Me) "SyncLock (aFork)"

    29 إذا mForkAvailable بعد ذلك

    30 أ.HasFork = صحيح

    31 مالك = أ اسمي

    32 mForkAvailable = خاطئة

    33 Console.WriteLine (a.MyName & "حصلت للتو على fork.waiting")

    34 جرب

    Thread.Sleep (100) Catch e As Exception Console.WriteLine (e.StackTrace)

    حاول إنهاء

    35 نهاية إذا

    36 Monitor.Exit (أنا)

    إنهاء SyncLock

    37 End Sub

    38 نهاية الفصل

    39 سكين من الدرجة العامة

    40 mKnifeAvailable مشترك خاص كمنطق منطقي = صحيح

    41 مالك مشترك خاص كسلسلة = "لا أحد"

    42 ملكية خاصة للقراءة فقط تمتلكUtensi1 () كسلسلة

    43 احصل

    44 عودة المالك

    45 End Get

    46- نهاية الملكية

    47 Public Sub GrabKnifetByVal a as مبرمج)

    48 Console.WriteLine (Thread.CurrentThread.Name & _)

    "محاولة انتزاع السكين.")

    49 Console.WriteLine (Me.OwnsUtensil & "has the knife.")

    50 Monitor.Enter (لي) "SyncLock (aKnife)"

    51 إذا mKnifeAvailable بعد ذلك

    52 mKnifeAvailable = خطأ

    53 أ. HasKnife = صحيح

    54 مالك = a.MyName

    55 Console.WriteLine (a.MyName & "just got the knife.waiting")

    56 جرب

    خيط.نوم (100)

    Catch e كاستثناء

    Console.WriteLine (مثل StackTrace)

    حاول إنهاء

    57 نهاية إذا

    58 Monitor.Exit (أنا)

    59 End Sub

    60 نهاية الفئة

    61 مبرمج الطبقة العامة

    62 الخاص mName كسلسلة

    63 خاص mFork As Fork

    64 سكينة خاصة مشتركة

    65 mHasKnife الخاص باعتباره منطقيًا

    66 خاص mHasFork كمنطقي

    67 مشترك Sub جديد ()

    68 mFork = شوكة جديدة ()

    69 mKnife = سكين جديد ()

    70 نهاية الفرعية

    71 Public Sub New (ByVal theName As String)

    72 mName = الاسم

    73 نهاية الفرعية

    74 خاصية القراءة فقط العامة MyName () كسلسلة

    75 احصل

    76 عودة mName

    77 End Get

    78 إنهاء الملكية

    79 الملكية العامة HasKnife () As Boolean

    80 احصل

    81 إرجاع mHasKnife

    82 الحصول على إنهاء

    83 مجموعة (ByVal Value As Boolean)

    84 mHasKnife = القيمة

    85 مجموعة النهاية

    86 نهاية الملكية

    87 ملكية عامة HasFork () As Boolean

    88 احصل

    89 عودة mHasFork

    90 End Get

    91 مجموعة (ByVal Value As Boolean)

    92 mHasFork = القيمة

    93 نهاية المجموعة

    94 نهاية الملكية

    95 العامة Sub Eat ()

    96 افعلوا معي

    97 Console.Writeline (Thread.CurrentThread.Name & "موجود في مؤشر الترابط.")

    98 إذا رند ()< 0.5 Then

    99 mFork.GrabFork (لي)

    100 آخر

    101 mKnife.GrabKnife (لي)

    102 نهاية إذا

    الحلقة 103

    104 MsgBox (Me.MyName & "can eat!")

    105 mKnife = سكين جديد ()

    106 mFork = شوكة جديدة ()

    107 النهاية الفرعية

    108 نهاية الفئة

    ينشئ الإجراء الرئيسي الرئيسي (الأسطر 4-16) مثيلين من فئة المبرمج ثم يبدأ خيطين لتنفيذ طريقة تناول الطعام الحرجة لفئة المبرمج (الأسطر 95-108) ، الموصوفة أدناه. يحدد الإجراء الرئيسي أسماء سلاسل الرسائل ويقوم بإعدادها ؛ ربما يكون كل ما يحدث مفهومًا وبدون تعليق.

    يبدو رمز فئة Fork أكثر تشويقًا (الأسطر 17-38) (يتم تحديد فئة السكين المماثلة في السطور 39-60). يحدد السطران 18 و 19 قيم الحقول المشتركة ، والتي يمكنك من خلالها معرفة ما إذا كان القابس متاحًا حاليًا ، وإذا لم يكن كذلك ، فمن يستخدمه. خاصية ReadOnly OwnUtensi1 (الأسطر 20-24) مخصصة لأبسط نقل للمعلومات. تعتبر طريقة GrabFork "الاستيلاء على الشوكة" ، المحددة في الأسطر 25-27 ، من العناصر المركزية لفئة الشوكة.

    1. يقوم السطران 26 و 27 بطباعة معلومات التصحيح ببساطة إلى وحدة التحكم. في الكود الرئيسي للطريقة (الأسطر 28-36) ، تتم مزامنة الوصول إلى مفترق الطرق حسب الكائنحزام لي. نظرًا لأن برنامجنا يستخدم شوكة واحدة فقط ، فإن المزامنة عبر Me تضمن عدم تمكن أي خيطين من التقاطها في نفس الوقت. يحاكي أمر Slee "p (في الكتلة التي تبدأ في السطر 34) التأخير بين الاستيلاء على شوكة / سكين وبدء الوجبة. لاحظ أن الأمر Sleep لا يفتح الكائنات ويسرع فقط من حالات الجمود!
      ومع ذلك ، فإن الأكثر أهمية هو رمز فئة المبرمجين (الأسطر 61-108). تحدد الأسطر 67-70 مُنشئًا عامًا لضمان وجود شوكة وسكين واحد فقط في البرنامج. رمز الخاصية (الأسطر 74-94) بسيط ولا يحتاج إلى تعليق. يحدث الشيء الأكثر أهمية في طريقة Eat ، والتي يتم تنفيذها بواسطة خيطين منفصلين. تستمر العملية في حلقة حتى يلتقط تيار ما الشوكة مع السكين. في السطور 98-102 ، يمسك الجسم الشوكة / السكين بشكل عشوائي باستخدام استدعاء Rnd ، وهو ما يسبب الجمود. يحدث ما يلي:
      يتم استدعاء الخيط الذي ينفذ طريقة Eat في Tot ويدخل في الحلقة. يمسك السكين ويدخل في حالة انتظار.
    2. يستدعي الخيط الذي ينفذ طريقة Bob's Eat الحلقة ويدخلها. لا يمكنه الإمساك بالسكين ، لكنه يمسك بالشوكة ويدخل في وضع الاستعداد.
    3. يتم استدعاء الخيط الذي ينفذ طريقة Eat في Tot ويدخل في الحلقة. يحاول الإمساك بالشوكة ، لكن بوب قد أمسك بالشوكة بالفعل ؛ ينتقل الخيط إلى حالة الانتظار.
    4. يستدعي الخيط الذي ينفذ طريقة Bob's Eat الحلقة ويدخلها. حاول أن يمسك السكين ، لكن السكين تم القبض عليه بالفعل من قبل تحوت ؛ ينتقل الخيط إلى حالة الانتظار.

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

    أرز. 10.7. تحليل المآزق في نافذة الموضوع

    لذلك ، إذا قمنا بإزالة الاستدعاء إلى Rnd على السطر 98 واستبدله بالمقتطف

    mFork.GrabFork (لي)

    mKnife.GrabKnife (أنا)

    الجمود يختفي!

    التعاون على البيانات عند إنشائها

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

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

    • يستيقظ مؤشر الترابط 1 (المستهلك) ، ويدخل طريقة متزامنة ، ويبحث عن البيانات ، ولا يجدها ، وينتقل إلى حالة الانتظار. مبدئياجسديا ، يجب عليه إزالة الحجب حتى لا يتعارض مع عمل الخيط المزود.
    • الخيط 2 (المزود) يدخل طريقة متزامنة تم تحريرها بواسطة مؤشر ترابط 1 ، يخلقبيانات الدفق 1 وإعلام الدفق 1 بطريقة أو بأخرى عن وجود البيانات. ثم يقوم بتحرير القفل حتى يتمكن الخيط 1 من معالجة البيانات الجديدة.

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

    تعتبر علاقات الموفر / المستهلك شائعة جدًا ، لذلك يتم إنشاء بدائل خاصة لمثل هذه المواقف في مكتبات فئة البرمجة متعددة مؤشرات الترابط. في NET ، تسمى هذه العناصر الأولية Wait و Pulse-PulseAl 1 وهي جزء من فئة Monitor. يوضح الشكل 10.8 الموقف الذي نحن على وشك برمجته. ينظم البرنامج ثلاث قوائم انتظار: قائمة انتظار وقائمة انتظار وقائمة انتظار تنفيذية. لا يقوم برنامج جدولة مؤشر الترابط بتخصيص وقت وحدة المعالجة المركزية إلى مؤشرات الترابط الموجودة في قائمة انتظار الانتظار. لتخصيص الوقت لمؤشر الترابط ، يجب أن ينتقل إلى قائمة انتظار التنفيذ. نتيجة لذلك ، يتم تنظيم عمل التطبيق بشكل أكثر كفاءة مما هو عليه عند استقصاء متغير شرطي.

    في الشفرة الكاذبة ، تتم صياغة لغة مستهلك البيانات على النحو التالي:

    "الدخول في كتلة متزامنة من النوع التالي

    بينما لا توجد بيانات

    اذهب إلى قائمة الانتظار

    حلقة

    إذا كانت هناك بيانات ، قم بمعالجتها.

    ترك كتلة متزامنة

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

    في الشفرة الكاذبة ، تبدو لغة موفر البيانات كما يلي:

    "الدخول في كتلة عرض متزامنة

    بينما البيانات ليست هناك حاجة

    اذهب إلى قائمة الانتظار

    إنتاج البيانات الأخرى

    عندما تكون البيانات جاهزة ، اتصل بـ Pulse-PulseAll.

    لنقل مؤشر ترابط واحد أو أكثر من قائمة انتظار الحظر إلى قائمة انتظار التنفيذ. اترك كتلة متزامنة (والعودة إلى قائمة انتظار التشغيل)

    لنفترض أن برنامجنا يحاكي عائلة مع أحد الوالدين الذي يكسب المال وطفل ينفق هذه الأموال. عندما ينتهي المالاتضح أن الطفل يجب أن ينتظر وصول مبلغ جديد. يبدو تنفيذ البرنامج لهذا النموذج كما يلي:

    1 خيار صارم

    2 نظام الواردات

    3 وحدة نمطية

    4 فرعي ()

    5 خافت العائلة كعائلة جديدة ()

    6 العائلة. StartltsLife ()

    7 نهاية الفرعية

    8 نهاية fjodule

    9

    10 عائلة من الدرجة العامة

    11 المال الخاص باعتباره عددًا صحيحًا

    12 أسبوعًا خاصًا بشكل صحيح = 1

    13 عام Sub StartltsLife ()

    14 خافت في الخيط - ابدأ كموضوع جديد

    15 خافت للخيط ابدأ كموضوع جديد

    16 خافت الخيط كخيط جديد (aThreadStart)

    17 خافت خيط خيط جديد (bThreadStart)

    18 aThread.Name = "إنتاج"

    19 أ - الخيط ابدأ ()

    20 bThread.Name = "استهلاك"

    21 ب الموضوع. يبدأ ()

    22 End Sub

    23 الملكية العامة TheWeek () بشكل صحيح

    24 احصل

    25 عودة أسبوع

    26 End Get

    27 مجموعة (ByVal Value As Integer)

    28 أسبوعًا - القيمة

    29 مجموعة النهاية

    30 عقار نهائي

    31 الملكية العامة OurMoney () باعتبارها عددًا صحيحًا

    32 احصل

    33 إرجاع المال

    34 End Get

    35 مجموعة (قيمة ByVal كعدد صحيح)

    36 ملي = القيمة

    37 مجموعة النهاية

    38 نهاية الملكية

    39 الإنتاج الفرعي العام ()

    40 خيط النوم (500)

    41 هل

    42 Monitor.Enter (أنا)

    43 افعل بينما أنا ، أموالنا> 0

    44 الشاشة. انتظر (لي)

    45 حلقة

    46 Me.OurMoney = 1000

    الحلقة 47

    48 Monitor.Exit (أنا)

    49 حلقة

    50 نهاية فرعية

    51 الاستهلاك العام ()

    52 MsgBox ("Am in consume thread")

    53 هل

    54 Monitor.Enter (أنا)

    55 افعلها بينما Me.OurMoney = 0

    56 Monitor.Wait (لي)

    57 حلقة

    58 Console.WriteLine ("والدي العزيز لقد أنفقت للتو كل ما تبذلونه من" & _

    المال في الأسبوع "و TheWeek)

    59 الأسبوع + = 1

    60 إذا كان TheWeek = 21 * 52 ثم System.Environment.Exit (0)

    61 Me.OurMoney = 0

    62 - جهاز العرض PulseAll (Me)

    63 Monitor.Exit (أنا)

    64 حلقة

    65 نهاية الفرعية

    66 نهاية الفئة

    تستعد طريقة StartltsLife (الأسطر 13-22) لبدء تدفقات الإنتاج والاستهلاك. يحدث الشيء الأكثر أهمية في تدفقات الإنتاج (الأسطر 39-50) والاستهلاك (الأسطر 51-65). يتحقق إجراء Sub Produce من توافر المال ، وإذا كان هناك مال ، فإنه ينتقل إلى قائمة الانتظار. خلاف ذلك ، يقوم الوالد بتوليد المال (السطر 46) وإخطار الكائنات الموجودة في قائمة الانتظار بالتغيير في الموقف. لاحظ أن استدعاء Pulse-Pulse All لا يسري إلا عند تحرير القفل باستخدام الأمر Monitor.Exit. على العكس من ذلك ، يتحقق إجراء Sub Consume من توافر المال ، وإذا لم يكن هناك مال ، يخطر الوالد المتوقع بذلك. السطر 60 ينهي البرنامج ببساطة بعد 21 سنة مشروطة ؛ نظام الاتصال. Environment.Exit (0) هو .NET analogue لأمر End (الأمر End مدعوم أيضًا ، ولكن على عكس System. Environment. Exit ، لا يُرجع رمز الخروج إلى نظام التشغيل).

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

    تعدد في برامج الرسومات

    تبدأ مناقشتنا حول تعدد مؤشرات الترابط في تطبيقات واجهة المستخدم الرسومية بمثال يشرح الغرض من تعدد مؤشرات الترابط في تطبيقات واجهة المستخدم الرسومية. قم بإنشاء نموذج باستخدام زرين Start (btnStart) و Cancel (btnCancel) ، كما هو موضح في الشكل. 10.9. يؤدي النقر فوق الزر "ابدأ" إلى إنشاء فئة تحتوي على سلسلة عشوائية من 10 ملايين حرف وطريقة لحساب تكرارات الحرف "E" في تلك السلسلة الطويلة. لاحظ استخدام فئة StringBuilder لإنشاء سلاسل طويلة بشكل أكثر كفاءة.

    الخطوة 1

    لاحظ موضوع 1 أنه لا توجد بيانات له. يستدعي الانتظار ، ويطلق القفل ، وينتقل إلى قائمة الانتظار.



    الخطوة 2

    عند تحرير القفل ، يترك الخيط 2 أو الخيط 3 قائمة انتظار الكتلة ويدخل في كتلة متزامنة ، ويحصل على القفل

    الخطوه 3

    لنفترض أن مؤشر الترابط 3 يدخل كتلة متزامنة ، ويقوم بإنشاء بيانات ، ويستدعي Pulse-Pulse All.

    مباشرة بعد الخروج من الكتلة وتحرير القفل ، يتم نقل الخيط 1 إلى قائمة انتظار التنفيذ. إذا كان الخيط 3 يستدعي Pluse ، يدخل واحد فقط في قائمة انتظار التنفيذالموضوع ، عندما يتم استدعاء Pluse All ، تنتقل جميع مؤشرات الترابط إلى قائمة انتظار التنفيذ.



    أرز. 10.8. مشكلة المزود / المستهلك

    أرز. 10.9. تعدد في تطبيق بسيط واجهة المستخدم الرسومية

    نظام الواردات

    فئة عامة عشوائية الأحرف

    m_Data الخاص باسم StringBuilder

    mjength الخاص ، m_count كعدد صحيح

    عام Sub جديد (ByVal n As Integer)

    م الطول = ن -1

    m_Data = جديد StringBuilder (m_length) MakeString ()

    End Sub

    سلسلة فرعية خاصة ()

    خافت أنا كعدد صحيح

    خافت myRnd كعشوائية جديدة ()

    لأني = 0 إلى m_length

    "أنشئ رقمًا عشوائيًا بين 65 و 90 ،

    "تحويلها إلى أحرف كبيرة

    "وإرفاقها بكائن StringBuilder

    m_Data.Append (Chr (myRnd.Next (65.90)))

    التالي

    End Sub

    عدد بدء فرعي عام ()

    GetEes ()

    End Sub

    Sub GetEes الخاصة ()

    خافت أنا كعدد صحيح

    لأني = 0 إلى m_length

    إذا كان m_Data.Chars (i) = CChar ("E") إذن

    m_count + = 1

    انتهى إذا التالي

    m_CountDone = صحيح

    End Sub

    للقراءة العامة فقط

    خاصية GetCount () كـ عدد صحيح Get

    إن لم يكن (m_CountDone) إذن

    إرجاع m_count

    إنهاء إذا

    إنهاء الحصول على الممتلكات

    للقراءة العامة فقط

    الخاصية IsDone () كما Boolean Get

    يعود

    تم

    إنهاء Get

    نهاية الممتلكات

    فئة النهاية

    هناك رمز بسيط جدًا مرتبط بالزرّين الموجودين في النموذج. ينشئ الإجراء btn-Start_Click فئة RandomCharacters المذكورة أعلاه ، والتي تغلف سلسلة مكونة من 10 ملايين حرف:

    Private Sub btnStart_Click (ByVal sender As System.Object.

    ByVal e باسم System.EventArgs) يعالج btnSTart.Click

    خافت RC كأحرف عشوائية جديدة (10000000)

    RC.StartCount ()

    MsgBox ("عدد es هو & RC.GetCount)

    End Sub

    يعرض الزر "إلغاء" مربع رسالة:

    Private Sub btnCancel_Click (المرسل ByVal As System.Object._

    ByVal e As System.EventArgs) يعالج btnCancel. انقر

    MsgBox ("Count Interrupt!")

    End Sub

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

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

    Application.DoEvents ()

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

    الخطوة التالية: إظهار زر العد

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

    أرز. 10.10. شكل زر مغلق

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

    فئة عامة عشوائية الأحرف

    m_0ata الخاص كـ StringBuilder

    m_CountDone الخاص باعتباره منطقيًا

    mjength الخاص. m_count كعدد صحيح

    زر m الخاص مثل Windows.Forms.Button

    عام Sub جديد (ByVa1 n As Integer، _

    ByVal b مثل Windows.Forms.Button)

    m_length = n - 1

    m_Data = جديد StringBuilder (mJength)

    m_Button = b MakeString ()

    End Sub

    سلسلة فرعية خاصة ()

    خافت أنا كعدد صحيح

    خافت myRnd كعشوائية جديدة ()

    لأني = 0 إلى m_length

    m_Data.Append (Chr (myRnd.Next (65.90)))

    التالي

    End Sub

    عدد بدء فرعي عام ()

    GetEes ()

    End Sub

    Sub GetEes الخاصة ()

    خافت أنا كعدد صحيح

    لأني = 0 إلى mjength

    إذا كان m_Data.Chars (I) = CChar ("E") إذن

    m_count + = 1

    انتهى إذا التالي

    m_CountDone = صحيح

    m_Button.Enabled = صحيح

    End Sub

    للقراءة العامة فقط

    خاصية GetCount () كعدد صحيح

    احصل على

    إن لم يكن (m_CountDone) إذن

    قم برمي استثناء جديد ("لم يتم العد بعد") عدا ذلك

    إرجاع m_count

    إنهاء إذا

    إنهاء Get

    نهاية الممتلكات

    تم تنفيذ الملكية العامة للقراءة فقط () على أنها قيمة منطقية

    احصل على

    إرجاع m_CountDone

    إنهاء Get

    نهاية الممتلكات

    فئة النهاية

    من المحتمل أن يعمل هذا الرمز في بعض الحالات. مع ذلك:

    • لا يمكن تنظيم تفاعل الخيط الثانوي مع مؤشر الترابط الذي ينشئ واجهة المستخدم الرسومية بديهييعني.
    • أبدالا تقم بتعديل العناصر الموجودة في برامج الرسومات من تدفقات البرامج الأخرى. يجب أن تحدث جميع التغييرات فقط في مؤشر الترابط الذي أنشأ واجهة المستخدم الرسومية.

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

    سيفشل أيضًا في تنظيم تفاعل الكائنات باستخدام الأحداث. يعمل عامل الحدث 06 على نفس مؤشر الترابط الذي تم استدعاء RaiseEvent لذلك لن تساعدك الأحداث.

    ومع ذلك ، فإن الحس السليم يفرض أن التطبيقات الرسومية يجب أن توفر وسيلة لتعديل العناصر من مؤشر ترابط آخر. في NET Framework ، توجد طريقة آمنة لمؤشر الترابط لاستدعاء طرق تطبيقات واجهة المستخدم الرسومية من مؤشر ترابط آخر. يتم استخدام نوع خاص من مفوض أسلوب Invoker من مساحة الاسم System.Windows لهذا الغرض. نماذج. يُظهر المقتطف التالي إصدارًا جديدًا من طريقة GetEes (تم تغيير الأسطر بخط غامق):

    Sub GetEes الخاصة ()

    خافت أنا كعدد صحيح

    لأني = 0 إلى m_length

    إذا كان m_Data.Chars (I) = CChar ("E") إذن

    m_count + = 1

    انتهى إذا التالي

    m_CountDone = صحيح حاول

    خافت mylnvoker كـ Methodlnvoker الجديد (AddressOf UpDateButton)

    myInvoker.Invoke () اكتشف e باسم ThreadlnterruptException

    "بالفشل

    حاول إنهاء

    End Sub

    زر تحديث فرعي عام ()

    m_Button.Enabled = صحيح

    End Sub

    لا يتم إجراء مكالمات Inter-thread على الزر بشكل مباشر ، ولكن من خلال Method Invoker. يضمن .NET Framework أن هذا الخيار آمن لمؤشر الترابط.

    لماذا هناك الكثير من المشاكل مع البرمجة متعددة مؤشرات الترابط؟

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

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

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

    ولكن هذا ليس كل شيء: تعمل المترجمات الحديثة على تحسين أداء البرنامج ، ويمكن أن تتداخل أجهزة الكمبيوتر مع إدارة الذاكرة. نتيجة لذلك ، يمكن للمجمع أو الجهاز ، دون علمك ، تغيير ترتيب الأوامر المحددة في الكود المصدري للبرنامج [ يعمل العديد من المجمعين على تحسين النسخ الدوري للمصفوفات مثل i = 0 إلى n: b (i) = a (i): ncxt. يمكن للمترجم (أو حتى مدير الذاكرة المتخصص) ببساطة إنشاء مصفوفة ثم تعبئتها بعملية نسخة واحدة بدلاً من نسخ العناصر الفردية عدة مرات!].

    نأمل أن تساعدك هذه التفسيرات على فهم أفضل لسبب تسبب البرمجة متعددة مؤشرات الترابط في حدوث الكثير من المشاكل - أو على الأقل مفاجأة أقل في السلوك الغريب لبرامجك متعددة مؤشرات الترابط!

    ما الموضوع الذي يطرح أكثر الأسئلة والصعوبات بالنسبة للمبتدئين؟ عندما سألت أستاذي ومبرمج Java Alexander Pryakhin عن هذا ، أجاب على الفور: "Multithreading". شكرا له على الفكرة وساعد في تحضير هذا المقال!

    سننظر في العالم الداخلي للتطبيق وعملياته ، ونكتشف ما هو جوهر تعدد مؤشرات الترابط ، ومتى يكون مفيدًا وكيفية تنفيذه - باستخدام Java كمثال. إذا كنت تتعلم لغة OOP مختلفة ، فلا تقلق: المبادئ الأساسية هي نفسها.

    حول تيارات وأصولها

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

    من المحتمل أنك صادفت "مدير المهام" في نظام التشغيل Windows (في نظام التشغيل Linux هو "مراقب النظام") وأنت تعلم أن العمليات الجارية غير الضرورية تؤدي إلى تحميل النظام ، وغالبًا ما يتم تجميد معظمها "ثقيلًا" ، لذلك يجب إنهاؤها قسرًا .

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

    نريد أن يقوم المعالج بتنفيذ المزيد من التعليمات ومعالجة المزيد من البيانات لكل وحدة زمنية. أي أننا بحاجة إلى احتواء المزيد من التعليمات البرمجية المنفذة في كل شريحة زمنية. فكر في وحدة تنفيذ التعليمات البرمجية ككائن - وهذا هو مؤشر ترابط.

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

    يحتوي كل تطبيق على عملية واحدة على الأقل ، ولكل عملية مؤشر ترابط واحد على الأقل ، يُسمى الخيط الرئيسي والذي ، إذا لزم الأمر ، يتم إطلاق خيوط جديدة.

    الفرق بين الخيوط والعمليات

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

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

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

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

    ما هو تعدد الخيوط

    لذلك نأتي إلى الشيء الرئيسي. تعدد العمليات هو عندما يتم تقسيم عملية التطبيق إلى مؤشرات ترابط تتم معالجتها بالتوازي - في وحدة زمنية واحدة - بواسطة المعالج.

    يتم توزيع الحمل الحسابي بين مركزين أو أكثر ، بحيث لا تؤدي الواجهة ومكونات البرنامج الأخرى إلى إبطاء عمل كل منهما.

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

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

    انتظر إشارة: التزامن في تطبيقات متعددة الخيوط

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

    أدوات التزامن الأساسية

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

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

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

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

    كيفية تنفيذ تعدد مؤشرات الترابط في جافا

    فئة Thread مسؤولة عن العمل مع الخيوط في Java. يعني إنشاء سلسلة رسائل جديدة لتنفيذ مهمة إنشاء مثيل لفئة مؤشر الترابط وربطها بالرمز الذي تريده. ويمكن أن يتم ذلك بطريقتين:

      فئة فرعية الموضوع

      تنفيذ الواجهة Runnable في الفصل الدراسي الخاص بك ، ومن ثم تمرير مثيلات الفئة إلى مُنشئ مؤشر الترابط.

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

    مثال على Java multithreading: بينج بونج مع كائنات المزامنة

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

    أولاً ، لنقم بإنشاء فئة ترث خصائص Thread التي نعرفها بالفعل ، ونكتب طريقة kickBall:

    يمتد PingPongThread للفئة العامة Thread (PingPongThread (String name) (this.setName (name) ؛ // تجاوز اسم الخيط)Override public void run () (Ball ball = Ball.getBall () ؛ while (ball.isInGame () ) (kickBall (ball) ؛)) kickBall باطل خاص (كرة) (if (! ball.getSide (). يساوي (getName ())) (ball.kick (getName ()) ؛)))

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

    الكرة من الفئة العامة (ركلات int الخاصة = 0 ؛ مثيل الكرة الثابت الخاص = كرة جديدة () ؛ جانب السلسلة الخاص = "" ؛ الكرة الخاصة () () الكرة الثابتة getBall () (نسخة الإرجاع ؛) ركلة باطلة متزامنة (اسم لعب السلسلة) (kicks ++؛ side = playername؛ System.out.println (kicks + "" + side)؛) سلسلة getSide () (جانب الإرجاع ؛) قيمة منطقية isInGame () (إرجاع (ركلات)< 15); } }

    والآن تدخل خيوط للاعبين إلى المشهد. دعنا نسميهم ، دون مزيد من اللغط ، بينج وبونج:

    فئة عامة PingPongGame (PingPongThread player1 = new PingPongThread ("Ping") ؛ PingPongThread player2 = new PingPongThread ("Pong")؛ Ball ball؛ PingPongGame () (ball = Ball.getBall ()؛) يقوم اللاعب باطلة startGame () .start () ؛ player2.start () ؛))

    "ملعب ممتلئ بالناس - حان الوقت لبدء المباراة". سنعلن رسميًا عن افتتاح الاجتماع - في الفصل الرئيسي للتطبيق:

    فئة عامة PingPong (عامة ثابتة باطلة رئيسية (String args))

    كما ترون ، لا يوجد شيء غاضب هنا. هذه مجرد مقدمة لتعدد مؤشرات الترابط في الوقت الحالي ، ولكنك تعرف بالفعل كيف تعمل ، ويمكنك تجربة - تحديد مدة اللعبة ليس بعدد الضربات ، ولكن بالوقت ، على سبيل المثال. سنعود إلى موضوع تعدد العمليات لاحقًا - سنلقي نظرة على الحزمة java.util.concurrent ومكتبة Akka والآلية المتغيرة. دعنا نتحدث أيضًا عن تنفيذ multithreading في Python.

    كلاي بريشيرس

    مقدمة

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

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

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

    القاعدة 1. حدد العمليات التي يتم إجراؤها في رمز البرنامج بشكل مستقل عن بعضها البعض

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

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

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

    القاعدة 2. تطبيق التوازي بمستوى منخفض من التفاصيل

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

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

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

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

    القاعدة 3. قم ببناء قابلية التوسع في التعليمات البرمجية الخاصة بك لتحسين الأداء مع نمو عدد النوى.

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

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

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

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

    القاعدة 4. استخدام مكتبات ترابط آمنة

    إذا كنت قد تحتاج إلى مكتبة للتعامل مع النقاط الفعالة للبيانات في التعليمات البرمجية الخاصة بك ، فتأكد من استخدام وظائف خارج الصندوق بدلاً من التعليمات البرمجية الخاصة بك. باختصار ، لا تحاول إعادة اختراع العجلة من خلال تطوير مقاطع التعليمات البرمجية التي تم توفير وظائفها بالفعل في إجراءات محسّنة من المكتبة. تحتوي العديد من المكتبات ، بما في ذلك مكتبة Intel® Math Kernel Library (Intel® MKL) و Intel® Integrated Performance Primitives (Intel® IPP) ، بالفعل على وظائف متعددة الخيوط ومُحسّنة للمعالجات متعددة النواة.

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

    القاعدة 5. استخدام نموذج تعدد مؤشرات مناسب

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

    الجانب السلبي لتعدد مؤشرات الترابط الواضح هو استحالة التحكم الدقيق في مؤشر الترابط.

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

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

    القاعدة 6. لا ينبغي أن تعتمد نتيجة رمز البرنامج على تسلسل تنفيذ الخيوط المتوازية

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

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

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

    القاعدة 7. استخدام تخزين التدفق المحلي. قم بتعيين أقفال لمناطق بيانات محددة حسب الحاجة

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

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

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

    ماذا تفعل إذا كنت بحاجة إلى مزامنة الوصول إلى كمية كبيرة من البيانات ، على سبيل المثال ، إلى صفيف من 10000 عنصر؟ من المؤكد أن توفير قفل واحد للمصفوفة بأكملها يمثل عقبة في التطبيق. هل يجب عليك حقًا تنظيم قفل لكل عنصر على حدة؟ بعد ذلك ، حتى إذا كان 32 أو 64 مؤشر ترابط متوازي سيصل إلى البيانات ، فسيتعين عليك منع تعارضات الوصول إلى منطقة ذاكرة كبيرة إلى حد ما ، واحتمال حدوث مثل هذه التعارضات هو 1٪. لحسن الحظ ، هناك نوع من الوسط الذهبي ، يسمى "أقفال modulo". إذا تم استخدام أقفال N modulo ، فسيقوم كل منها بمزامنة الوصول إلى الجزء N من منطقة البيانات المشتركة. على سبيل المثال ، إذا تم تنظيم اثنين من هذه الأقفال ، سيمنع أحدهما الوصول إلى عناصر المصفوفة الزوجية ، والآخر - إلى العناصر الفردية. في هذه الحالة ، تحدد الخيوط ، بالرجوع إلى العنصر المطلوب ، تكافؤه وتعيين القفل المناسب. يتم تحديد عدد وحدات التأمين مع الأخذ في الاعتبار عدد الخيوط واحتمال الوصول المتزامن من خلال عدة مؤشرات ترابط إلى نفس منطقة الذاكرة.

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

    القاعدة 8. تغيير خوارزمية البرنامج إذا لزم الأمر لتنفيذ multithreading

    معيار تقييم أداء التطبيقات ، المتسلسلة والمتوازية ، هو وقت التنفيذ. كتقدير للخوارزمية ، يكون الترتيب المقارب مناسبًا. يكون هذا المقياس النظري مفيدًا دائمًا تقريبًا لتقييم أداء التطبيق. أي ، مع تساوي جميع الأشياء الأخرى ، سيتم تشغيل تطبيق بمعدل نمو O (n log n) (Quicksort) أسرع من تطبيق بمعدل نمو O (n2) (فرز انتقائي) ، على الرغم من نتائج هذه التطبيقات هي نفسها.

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

    كتوضيح للبيان الأخير ، ضع في اعتبارك ضرب مصفوفتين مربعتين. تحتوي خوارزمية Strassen على أحد أفضل أوامر التنفيذ المقاربة: O (n2.81) ، وهي أفضل بكثير من ترتيب O (n3) لخوارزمية الحلقات الثلاثية المتداخلة العادية. وفقًا لخوارزمية Strassen ، يتم تقسيم كل مصفوفة إلى أربعة مصفوفات فرعية ، وبعد ذلك يتم إجراء سبع مكالمات متكررة لمضاعفة n / 2 × n / 2 المصفوفات الفرعية. لموازاة الاستدعاءات المتكررة ، يمكنك إنشاء سلسلة رسائل جديدة تؤدي بشكل متسلسل سبع عمليات ضرب مستقلة للمصفوفات الفرعية حتى تصل إلى الحجم المحدد. في هذه الحالة ، سينمو عدد الخيوط بشكل كبير ، وستزداد دقة الحسابات التي يتم إجراؤها بواسطة كل خيط تم تشكيله حديثًا مع انخفاض حجم الشرائح الفرعية. لنفكر في خيار آخر - تنظيم مجموعة من سبعة خيوط تعمل في وقت واحد وتنفيذ عملية ضرب واحدة للمصفوفات الفرعية. عند إنهاء تجمع الخيوط ، يتم استدعاء طريقة Strassen بشكل متكرر لمضاعفة المراتب الفرعية (كما في الإصدار المتسلسل من رمز البرنامج). إذا كان النظام الذي يقوم بتشغيل مثل هذا البرنامج يحتوي على أكثر من ثمانية أنوية للمعالج ، فسيكون بعضها خاملاً.

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

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

    للعودة إلى صفحة ويب دروس البرمجة متعددة مؤشرات الترابط ، انتقل إلى