أين هي الساقين من Java Memory Model

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

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

لكن الجميع أدركوا بالفعل ، لأنه بطريقة ما يجب أن تتعايشوا معها. ومبرمجو جافا يعيشون بشكل جيد. لأن Java لديها نموذج ذاكرة - Java Memory Model (JMM) ، والذي يوفر قواعد بسيطة إلى حد ما لكتابة الكود الصحيح متعدد الخيوط.

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

ومطاردة التجريد


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



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

في المقالة ، أنا مهتم أكثر بالموضوعات التالية:

  • النظرية والمصطلحات
  • كيف تنعكس نظرية البرمجة متعددة مؤشرات الترابط في JMM
  • نماذج البرمجة التنافسية

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

ما أهمية معرفة النظرية؟ في رأيي ، آمل فقط بالنسبة لي ، لدى بعض المبرمجين رأي مفاده أن JMM هي تعقيد للغة وترقيع لبعض مشاكل النظام الأساسي في تعدد العمليات. تُظهر النظرية أن Java لم تعقد ، لكنها تبسّطت وجعلت برمجة متعددة مؤشرات الترابط معقدة للغاية ويمكن التنبؤ بها.

المنافسة والتزامن


أولاً ، دعونا نلقي نظرة على المصطلحات. لسوء الحظ ، لا يوجد إجماع في المصطلحات - عند دراسة مواد مختلفة ، قد تصادف تعاريف مختلفة للمنافسة والتزامن.

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

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

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

كثيرا ما يستشهد به روب بايك ، الذي يميز بين المفاهيم على النحو التالي:

  • المنافسة هي وسيلة لحل العديد من المشاكل في وقت واحد
  • التزامن هو وسيلة لأداء أجزاء مختلفة من مهمة واحدة.

رأي Rob Pike ليس معيارًا ، لكن في رأيي ، من المناسب البناء عليه لمواصلة دراسة المشكلة. اقرأ المزيد عن الاختلافات هنا .

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

علامات المنافسة.

  • وجود عدة تدفقات تحكم (على سبيل المثال ، Thread in Java ، coroutine في Kotlin) ، إذا كان هناك تدفق تحكم واحد فقط ، فلا يمكن أن يكون هناك تنفيذ تنافسي
  • نتيجة غير حتمية. تعتمد النتيجة على الأحداث العشوائية والتنفيذ وكيفية إجراء التزامن. حتى لو كان كل تيار حاسم تمامًا ، فإن النتيجة النهائية ستكون غير حتمية

سيكون للبرنامج الموازي مجموعة مختلفة من الميزات.

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

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

التزامن ممكن على مستوى:

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

التزامن في مستوى التعليمات هو مثال واحد من التحسينات التي تحدث مع تنفيذ التعليمات البرمجية المخفية عن المبرمج.

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

ليس كل ما يعمل في المسائل الموازية ل JMM. لا يعتبر التنفيذ المتزامن على مستوى التعليمات داخل سلسلة رسائل واحدة في JMM.

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

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

النماذج ذات الحالة المشتركة: "دوران العمليات" و "حدث من قبل"


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

يشتمل نموذج الحالة العام على حسابات مع المراسلة ، حيث تكون الحالة المشتركة قائمة انتظار للرسائل وحسابات مع ذاكرة مشتركة ، حيث تكون الحالة العامة عبارة عن بنيات في الذاكرة.

كل من الحسابات يمكن محاكاة.

يعتمد النموذج على آلة الحالة المحدودة. يركز النموذج بشكل حصري على الحالة المشتركة ويتم تجاهل البيانات المحلية لكل من التدفقات تمامًا. كل إجراء يتدفق عبر حالة مشتركة هو وظيفة الانتقال إلى حالة جديدة.

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

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

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

يسمى هذا النموذج نموذج الأداء من خلال تناوب العمليات (تم سماع الاسم في تقرير روماني إليزاروف).

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

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

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

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

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

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

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

ونحن حقا لا نستطيع رؤية نتيجة التسجيل ، أي في متغير قيمته 0 في الدفق P ، نكتب 1 ، وفي الدفق Q نقرأ هذا المتغير. بغض النظر عن مقدار الوقت المادي الذي يمر بعد التسجيل ، لا يزال بإمكاننا قراءة 0 .

هذه هي الطريقة التي تعمل بها أجهزة الكمبيوتر والنموذج يعكس هذا.

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

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

محاولة تحليل برنامج جافا


لقد اعتبرنا الحد الأدنى نظريًا ، فلنحاول المضي قدمًا في برنامج متعدد الخيوط بلغة معينة - Java ، من خيطين مع حالة مشتركة قابلة للتغيير.

مثال كلاسيكي.

private static int x = 0, y = 0; private static int a = 0, b = 0; synchronized (this) { a = 0; b = 0; x = 0; y = 0; } Thread p = new Thread(() -> { a = 1; x = b; }); Thread q = new Thread(() -> { b = 1; y = a; }); p.start(); q.start(); p.join(); q.join(); System.out.println("x=" + x + ", y=" + y); 

نحن بحاجة إلى محاكاة تنفيذ هذا البرنامج والحصول على جميع النتائج الممكنة - قيم المتغيرات x و y. ستكون هناك العديد من النتائج ، كما نتذكر من الناحية النظرية ، مثل هذا البرنامج غير حاسم.

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

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

ما هي الخيارات؟

تجاهل القيود ومحاكاة التشذير. يمكنك أن تجرب ذلك ، ربما لن يحدث شيء سيء.

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

يمكن تمثيل جميع عمليات تنفيذ البرامج الممكنة كآلية حالة محدودة.



كل دائرة هي حالة النظام ، في حالتنا المتغيرات a ، b ، x ، y . وظيفة الانتقال هي إجراء على حالة تضع النظام في حالة جديدة. نظرًا لأن عمليتي تدفق يمكنهما تنفيذ إجراءات على الحالة العامة ، فسيكون هناك انتقالان من كل ولاية. الدوائر المزدوجة هي الحالات النهائية والأولية للنظام.

في المجموع ، من الممكن تنفيذ 6 عمليات إعدام مختلفة ، مما ينتج عنه أزواج من قيم x و y:
(1, 1), (1, 0), (0, 1)



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

لاختبار البرامج التنافسية ، من الأفضل استخدام أدوات خاصة ( أداة ، تقرير ).

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

إذا قمنا بتشغيل الكود على بنية أحادية النواة أو أحادية المعالج ، فعندئذ يجب أن نحصل على النتيجة من المجموعة التي نتوقعها. نموذج الدوران سوف يعمل بشكل جيد. في البنية متعددة النواة ، على سبيل المثال ، x86 ، قد نفاجأ بالنتيجة - يمكننا الحصول على النتيجة (0،0) ، والتي لا يمكن أن تكون وفقًا لنمذجةنا.

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

نظرية البرمجة التنافسية و JMM


حان الوقت لإلقاء نظرة فاحصة على علاقة "حدث من قبل" وكيفية تكوين صداقات مع JMM. يمكن العثور على التعريف الأصلي لعلاقة "حدث من قبل" في الوقت والساعات وترتيب الأحداث في نظام موزع.

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

هناك أوامر مختلفة في JMM. يتحدث أليكسي شيبيليف بقوة عن القواعد في أحد تقاريره .

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

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

في مثالنا ، نحصل على شيء مثل التالي:

P: a = 1 PO x = b - الكتابة إلى a والقراءة b لها أمر PO
Q: b = 1 PO y = a - الكتابة إلى ب وقراءة أمر ص

تجسست على هذا النوع من الكتابة ث (أ ، 1) ص ص (ب): 0. آمل حقًا ألا يكون أحد قد حصل على براءة اختراع للتقارير. ومع ذلك ، فإن المواصفات لديها شكل مماثل.

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

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

الفكرة بسيطة - في الوقت الذي نقرأ فيه المتغير في الدفق Q ، قد لا ينتهي سجل هذا المتغير نفسه في الدفق P بعد. وبغض النظر عن مقدار الوقت المادي الذي تشاركه هذه الأحداث - nanosecond أو بضع ساعات.

لطلب الأحداث ، نحتاج إلى علاقة "حدثت من قبل". JMM يعرف هذه العلاقة. تحدد المواصفات الترتيب في موضوع واحد:

إذا كانت العملية x و y موجودة في نفس مؤشر الترابط وفي PO x تحدث أولاً ، ثم y ، فإن x حدث قبل y.


في المستقبل ، يمكننا أن نقول أنه يمكننا استبدال جميع أوامر الشراء بـ يحدث قبل ( HB ):

 P: w(a, 1) HB r(b) Q: w(b, 1) HB r(a) 

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

ترتيب التزامن ( SO ) - ارتباطات إجراءات التزامن ( SA ) ، ترد قائمة كاملة من SA في المواصفات ، في القسم 17.4.2. الإجراءات هؤلاء بعض منهم:

  • قراءة متغير متقلبة
  • كتابة متغير متقلبة
  • قفل الشاشة
  • فتح الشاشة

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

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

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

فكر في أمر Synchronizes-With Order ( SW ) آخر - ترتيب SO لإلغاء القفل / القفل ، اكتب / اقرأ أزواجًا متقلبة. لا يهم التدفقات التي ستكون عليها هذه الإجراءات ، الشيء الرئيسي هو أنها موجودة على نفس الشاشة ، متغير متغير. يوفر SW جسر بين المواضيع.

والآن نأتي إلى الترتيب الأكثر إثارة للاهتمام - يحدث قبل ( HB ).
HB هو إغلاق متعدية لاتحاد SW و PO . PO يعطي ترتيبًا خطيًا داخل الدفق ، ويوفر SW جسرًا بين الجداول. HB متعدّد ، أي لو

 x HB y  y HB z,  x HB z 

المواصفات لديها قائمة بعلاقات HB ، يمكنك التعرف عليها بمزيد من التفاصيل ، وفيما يلي بعض من القائمة:

داخل خيط واحد ، تحدث أي عملية - قبل أي عملية تتبعها في الكود المصدري.

يحدث خروج كتلة / طريقة متزامنة - قبل إدخال كتلة / طريقة متزامنة على نفس الشاشة.

كتابة حقل متقلّب يحدث قبل قراءة نفس الحقل المتقلب .

دعنا نعود إلى مثالنا:

 P: a = 1 PO x = b Q: b = 1 PO y = a 

دعنا نعود إلى مثالنا ومحاولة تحليل البرنامج ، مع مراعاة الطلبات.

يعتمد تحليل البرنامج باستخدام JMM على طرح أي فرضيات وتأكيدها أو دحضها.



نبدأ تحليلنا بفرضية أنه لا يوجد تنفيذ برنامج واحد يعطي النتيجة (0 ، 0). يعد عدم وجود نتيجة (0 ، 0) على جميع عمليات الإعدام خاصية مفترضة للبرنامج.

نحن نختبر الفرضية من خلال بناء عمليات إعدام مختلفة.

لقد رصدت التسميات هنا (في بعض الأحيان تظهر بدلاً من race الكلمات مع سهم ، يستخدم Alexey نفسه سباق السهم والكلمة في تقاريره ، لكن يحذر من أن هذا الترتيب غير موجود في JMM ويستخدم هذا الرمز للتوضيح).

نحن نبدي تحفظ صغير.

نظرًا لأن جميع الإجراءات المتعلقة بالمتغيرات العامة مهمة بالنسبة لنا ، وفي المثال ، المتغيرات الشائعة هي أ ، ب ، س ، ص . ثم ، على سبيل المثال ، يجب اعتبار العملية x = b بمثابة r (b) و w (x ، b) ، و r(b) HB w(x,b) (على أساس PO ). ولكن نظرًا لأن المتغير x لا يقرأ في أي مكان في سلاسل الرسائل (القراءة في الطباعة في نهاية الكود ليست مثيرة للاهتمام ، لأنه بعد عملية الربط في الخيط سنرى القيمة x) ، لا يمكننا النظر في الإجراء w (x ، b).

تحقق من الأداء الأول.

 w(a, 1) HB r(b): 0 … w(b, 1) HB r(a): 0 

في الدفق Q ، نقرأ المتغير a ، اكتب إلى هذا المتغير في الدفق P. لا يوجد ترتيب بين الكتابة والقراءة (PO، SW، HB) .

إذا كان المتغير مكتوبًا في مؤشر ترابط واحد وكانت القراءة في مؤشر ترابط آخر ولم تكن هناك علاقة HB بين العمليات ، فيقولون إن المتغير يُقرأ تحت العرق. وتحت السباق وفقًا لـ JMM ، يمكننا قراءة آخر قيمة مسجلة في HB ، أو أي قيمة أخرى.

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

ليس من المنطقي التفكير في بقية التنفيذ ، لأن الفرضية قد تم تدميرها بالفعل .

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

لإثبات أن البرنامج ليس سباقًا ، يجب عليك القيام بذلك لجميع العروض. دعونا نحاول جعل SA ووضع علامة على متغير مع معدل متغير . المتغيرات المتقلبة ستكون مسبوقة بـ v.

لقد طرحنا فرضية جديدة . إذا كان المتغير a متقلبًا ، فلن ينتج عن تنفيذ البرنامج النتيجة (0 ، 0).

 w(va, 1) HB r(b): 0 … w(b, 1) HB r(va): 0 

التنفيذ لا ينتهك JMM . قراءة va يحدث تحت السباق. أي سباق يدمر عبور HB.

وضعنا فرضية أخرى . إذا تم تغيير المتغير b ، فلن ينتج عن تنفيذ البرنامج النتيجة (0 ، 0).

 w(a, 1) HB r(vb): 0 … w(vb, 1) HB r(a): 0 

التنفيذ لا ينتهك JMM. قراءة يحدث تحت السباق.

دعنا نختبر فرضية أنه إذا كانت المتغيرات a و b متقلبة ، فلن يعطي تنفيذ البرنامج النتيجة (0 ، 0).

تحقق من الأداء الأول.

 w(va, 1) SO r(vb): 0 SO w(vb, 1) SO r(va): 0 

نظرًا لأن جميع الإجراءات في برنامج SA (تحديداً قراءة أو كتابة متغير متغير) ، نحصل على ترتيب SO الكامل بين جميع الإجراءات. هذا يعني أن r (va) يجب أن ترى w (va، 1). هذا التنفيذ ينتهك JMM .

من الضروري المتابعة إلى التنفيذ التالي لتأكيد الفرضية. ولكن نظرًا لوجود SO لأي تنفيذ ، فيمكنك الخروج عن الإجراءات الشكلية - من الواضح أن النتيجة (0 ، 0) تنتهك JMM لأي تنفيذ.

لاستخدام نموذج التدوير ، تحتاج إلى إضافة متقلبة للمتغيرات a و b. سيعطي مثل هذا البرنامج النتائج (1،1) أو (1،0) أو (0،1).

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

لكن يصعب تحليل البرامج المعقدة التي تضم عددًا كبيرًا من عمليات الإعدام والبيانات المشتركة ، نظرًا لأنك تحتاج إلى التحقق من جميع عمليات الإعدام.

نماذج تنفيذ تنافسية أخرى


لماذا النظر في نماذج البرمجة التنافسية الأخرى؟

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

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

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

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

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

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

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

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

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

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

لماذا هو نموذج خيوط شعبية جدا؟


لقد درسنا نموذجين للبرمجة التنافسية ، في الواقع ، هناك الكثير منها يجعل البرمجة التنافسية أسهل وأكثر أمانًا.

ولكن لماذا إذن لا تزال الخيوط والأقفال مشهورة جدًا؟

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

في وقت من الأوقات ، يمكن للنواة تنفيذ تعليمة واحدة (في الواقع لا ، يوجد التزامن على مستوى التعليمات ، ولكن هذا لا يهم الآن) ، ولكن بسبب تعدد المهام ، حتى على الأجهزة أحادية المركز ، يمكن تنفيذ العديد من البرامج في وقت واحد (بالطبع الزائفة في وقت واحد).

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

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

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

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

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

ما هي التقنيات الموجودة لحل المشكلة؟

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

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

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

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

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

الاستنتاجات


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

مواد إضافية


حاولت أن أضع في نص روابط المقال إلى المصادر التي حصلت منها على معلومات.

بشكل عام ، من السهل العثور على مواد JMM على الإنترنت. هنا ، سأنشر روابط لبعض المواد الإضافية المرتبطة بـ JMM وقد لا أشترك على الفور.

قراءة

  • مدونة Alexey Shipilev - أعرف ما هو واضح ، لكنها مجرد خطيئة ناهيك عنها
  • مدونة Cheremin Ruslan - لم يكتب بشكل نشط مؤخرًا ، تحتاج إلى البحث عن إدخالاته القديمة في المدونة ، صدقوني أنها تستحق ذلك - هناك نافورة
  • Habr Gleb Smirnov - هناك مقالات ممتازة حول تعدد العمليات ونموذج الذاكرة
  • — , . , .



, . JMM, , . , JMM, .


فيديو

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

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


All Articles