مقدمة
تحدثت المقالة السابقة عن مبدأ CAS ، الذي ذكر فئة* الذرية. تعتمد آلية تنفيذ العمليات الذرية على خصائص رؤية الذاكرة المتطايرة. إذا كنت لا تعرف CAS و Atomic* حتى الآن ، فمن المستحسن إلقاء نظرة على ما نتحدث عنه.
ثلاث خصائص للتزامن
بادئ ذي بدء ، إذا كنا نريد استخدام التقلب ، فيجب أن يكون في بيئة تزامن متعددة الخيوط. هناك ثلاث خصائص مهمة في السيناريو المتزامن الذي نتحدث عنه غالبًا: الذرة والرؤية والنظام. فقط عندما يتم استيفاء هذه الخصائص الثلاثة ، يمكن تنفيذ البرنامج المتزامن بشكل صحيح ، وإلا ستنشأ مشاكل مختلفة.
يمكن أن تضمن الطبقات Atomicity و CAS و Atomic* المذكورة في المقالة السابقة ذرة العمليات البسيطة. بالنسبة لبعض العمليات المسؤولة ، يمكن تنفيذها باستخدام أقفال متزامنة أو مختلفة.
تشير الرؤية إلى متى تصل مؤشرات الترابط المتعددة إلى نفس المتغير ، يقوم مؤشر ترابط واحد بتعديل قيمة المتغير ، ويمكن أن ترى مؤشرات الترابط الأخرى على الفور القيمة المعدلة.
الطلب ، يتم تنفيذ ترتيب تنفيذ البرنامج بترتيب الكود ، ويحظر إعادة ترتيب التعليمات. يبدو من الطبيعي أن هذا ليس هو الحال. إعادة ترتيب التعليمات هي JVM لتحسين التعليمات وتحسين كفاءة تشغيل البرنامج ، ولتحسين التوازي قدر الإمكان دون التأثير على نتائج تنفيذ برنامج واحد. ومع ذلك ، في بيئة متعددة الخيوط ، قد يتسبب ترتيب بعض الرموز في عدم قصر منطقي.
يتنفس المتقلبة من ميزتين ، الرؤية والنظام. لذلك ، في بيئة متعددة الخيوط ، من الضروري ضمان وظيفة هاتين الميزتين ، ويمكن استخدام الكلمة الرئيسية المتطايرة.
كيف يضمن التقلب الرؤية
عندما يتعلق الأمر بالرؤية ، تحتاج إلى فهم معالج الكمبيوتر والذاكرة الرئيسية. بسبب متعدد الخيوط ، بغض النظر عن عدد المواضيع الموجودة هناك ، سيتم تنفيذها في النهاية في معالج الكمبيوتر. أجهزة الكمبيوتر اليوم متعددة النواة ، وبعض الآلات لديها حتى المعالجة المتعددة. دعونا نلقي نظرة على مخطط بنية المعالجات المتعددة:
هذا هو وحدة المعالجة المركزية مع اثنين من المعالجات ، رباعي النواة. يتوافق المعالج مع فتحة مادية ، ويتم توصيل المعالجات المتعددة من خلال ناقل QPI. يتكون المعالج من نوى متعددة ، وذاكرة التخزين المؤقت L3 المشتركة متعددة النواة بين المعالجات. يحتوي النواة على سجلات ، ذاكرة التخزين المؤقت L1 ، ذاكرة التخزين المؤقت L2.
أثناء تنفيذ البرنامج ، يجب إشراك قراءة وكتابة البيانات. نعلم جميعًا أنه على الرغم من أن سرعة وصول الذاكرة سريعة جدًا بالفعل ، إلا أنها لا تزال أدنى من سرعة تعليمات تنفيذ وحدة المعالجة المركزية. لذلك ، في kernel و L1 و L2 و L3 تتم إضافة ذاكرة التخزين المؤقت من المستوى الثالث. وبهذه الطريقة ، عند تشغيل البرنامج ، يتم نسخ البيانات المطلوبة لأول مرة من الذاكرة الرئيسية إلى ذاكرة التخزين المؤقت للنواة ، وبعد اكتمال العملية ، يتم كتابة ذلك إلى الذاكرة الرئيسية. الشكل التالي هو مخطط تخطيطي للبيانات التي تصل إلى وحدة المعالجة المركزية ، من السجلات إلى ذاكرة التخزين المؤقت إلى الذاكرة الرئيسية وحتى الأقراص الصلبة ، تصبح السرعة أبطأ وأبطأ.
بعد فهم بنية وحدة المعالجة المركزية ، دعونا نلقي نظرة على العملية المحددة لتنفيذ البرنامج ونعمل عملية مملوءة بالذات بسيطة كمثال.
i = i+1 ؛
عند تنفيذ هذا البيان ، يقوم مؤشر ترابط يعمل على الأساسية بنسخ قيمة I إلى ذاكرة التخزين المؤقت حيث يوجد اللب. بعد اكتمال العملية ، سيتم كتابتها مرة أخرى إلى الذاكرة الرئيسية. في بيئة متعددة الخيوط ، سيكون لكل مؤشر ترابط ذاكرة عاملة مقابلة في منطقة ذاكرة التخزين المؤقت في قلب الجري ، أي أن كل مؤشر ترابط له منطقة ذاكرة التخزين المؤقت الخاصة به الخاصة لتخزين بيانات النسخة المتماثلة المطلوبة للتشغيل. ثم ، دعونا نلقي نظرة على مشكلة i+1. على افتراض أن القيمة الأولية لـ I هي 0 ، هناك موضوعان يقومان بتنفيذ هذا البيان في نفس الوقت ، ويحتاج كل مؤشر ترابط ثلاث خطوات لتنفيذها:
1. اقرأ قيمة I من الذاكرة الرئيسية إلى ذاكرة العمل في مؤشر الترابط ، أي منطقة ذاكرة التخزين المؤقت Kernel المقابلة ؛
2. حساب قيمة i+1 ؛
3. اكتب قيمة النتيجة مرة أخرى إلى الذاكرة الرئيسية ؛
بعد تنفيذ الخيوطين 10000 مرة لكل منهما ، يجب أن تكون القيمة المتوقعة 20،000. لسوء الحظ ، فإن قيمة أنا دائمًا أقل من 20،000. أحد أسباب هذه المشكلة هو مشكلة تناسق ذاكرة التخزين المؤقت. على هذا المثال ، بمجرد تعديل نسخة ذاكرة التخزين المؤقت من مؤشر ترابط ، يجب إبطال نسخة ذاكرة التخزين المؤقت من مؤشرات الترابط الأخرى على الفور.
بعد استخدام الكلمة الرئيسية المتطايرة ، ستكون التأثيرات التالية:
1. في كل مرة يتم تعديل المتغير ، سيتم كتابة ذاكرة التخزين المؤقت للمعالج (الذاكرة العاملة) إلى الذاكرة الرئيسية ؛
2. إن الكتابة إلى الذاكرة الرئيسية للذاكرة العاملة ستؤدي إلى أن تكون ذاكرة التخزين المؤقت للمعالج (الذاكرة العاملة) من مؤشرات الترابط الأخرى غير صالحة.
نظرًا لأن المتقلبة يضمن رؤية الذاكرة ، فإنه يستخدم بالفعل بروتوكول MESI الذي يضمن تناسق ذاكرة التخزين المؤقت بواسطة وحدة المعالجة المركزية. هناك العديد من محتويات بروتوكول MESI ، لذلك لن أشرح ذلك هنا. يرجى التحقق من ذلك بنفسك. باختصار ، يتم استخدام الكلمة الرئيسية المتطايرة. عندما يتم كتابة تعديل مؤشر ترابط إلى المتغير المتطاير مرة أخرى إلى الذاكرة الرئيسية على الفور ، مما يتسبب في إبطال خط ذاكرة التخزين المؤقت من مؤشرات الترابط الأخرى ، ويضطر مؤشرات الترابط الأخرى إلى استخدام المتغير مرة أخرى ، يجب قراءتها من الذاكرة الرئيسية.
ثم نقوم بتعديل المتغير I أعلاه مع متقلبة وننفذه مرة أخرى ، وسيقوم كل مؤشر ترابط بتنفيذ 10000 مرة. لسوء الحظ ، لا يزال أقل من 20،000. لماذا هذا؟
يستخدم Folatile بروتوكول MESI الخاص بوحدة المعالجة المركزية لضمان الرؤية. ومع ذلك ، لاحظ أن المتقلبة لا تضمن ذرة العملية ، لأن عملية التمييز الذاتي هذه تنقسم إلى ثلاث خطوات. لنفترض أن الموضوع 1 يقرأ قيمة I من الذاكرة الرئيسية ، على افتراض أنه 10 ، ويحدث انسداد في هذا الوقت ، لكنني لم يتم تعديله بعد. في هذا الوقت ، يقرأ الموضوع 2 أيضًا قيمة I من الذاكرة الرئيسية. في هذا الوقت ، فإن القيمة I يقرأها هذين الخيطين هي نفسها ، على حد سواء 10 ، ثم يضيف الموضوع 2 1 إلى I ويكتبها إلى الذاكرة الرئيسية على الفور. في هذا الوقت ، وفقًا لبروتوكول MESI ، سيتم تعيين خط ذاكرة التخزين المؤقت المقابل للذاكرة العاملة للمعلومات 1 على حالة غير صالحة ، نعم. ومع ذلك ، يرجى ملاحظة أن الموضوع 1 قد نسخ بالفعل قيمة I من الذاكرة الرئيسية ، والآن يستغرق تشغيل 1 فقط إضافة 1 والكتابة إلى الذاكرة الرئيسية. يضيف كلا الموضوعين 1 على أساس 10 ثم اكتب مرة أخرى إلى الذاكرة الرئيسية ، وبالتالي فإن القيمة النهائية للذاكرة الرئيسية هي 11 فقط ، وليس 12 المتوقع.
لذلك ، يمكن أن يؤدي استخدام المتقلبة إلى ضمان رؤية الذاكرة ، ولكن لا يمكن أن يضمن الذرة. إذا كانت هناك حاجة إلى Atomicity ، فيمكنك الرجوع إلى هذه المقالة السابقة.
كيف يضمن التقلب النظام
يحتوي نموذج ذاكرة Java على بعض "Orderline" الفطرية ، أي أنه يمكن ضمانه دون أي وسيلة. هذا عادة ما يسمى مبدأ الحدوث. إذا كان لا يمكن اشتقاق ترتيب التنفيذ لعمليتين من مبدأ SECT-Be-Be-Frear ، فلا يمكنهم ضمان نظامهم ويمكن أن يعيد ترتيب الأجهزة الافتراضية حسب الرغبة.
فيما يلي 8 مبادئ من قبل-قبل ، مقتطف من "الفهم المتعمق للأجهزة الافتراضية Java".
سنتحدث هنا بشكل أساسي عن قواعد الكلمة الرئيسية المتطايرة ، ونقدم مثالًا على التحقق المزدوج في نمط المفرد الشهير:
Class Singleton {private platile Static Singleton مثيل = NULL ؛ خاص singleton () {} static singleton getInstance () {if (مثيل == null) {// الخطوة 1 Synchronized (singleton.class) {if (مثيل == null) // step 2 مثيل = جديد singleton () ؛ // الخطوة 3}} مثيل الإرجاع ؛ }}إذا لم يتم تعديل المثيل مع متقلبة ، فما هي النتائج التي يمكن إنتاجها؟ لنفترض أن هناك خيطا يدعون طريقة getInstance (). يقوم الموضوع 1 بتنفيذ Step1 ويجد أن المثيل فارغ ، ثم يغلق فئة Singleton بشكل متزامن. ثم يحدد ما إذا كان المثيل فارغًا مرة أخرى ، ويجد أنه لا يزال فارغًا ، ثم ينفذ الخطوة 3 ويبدأ في إنشاء مثيل له. أثناء عملية التثبيت ، يذهب الموضوع 2 إلى الخطوة 1 وقد يجد أن المثيل ليس فارغًا ، ولكن في هذا الوقت ، قد لا يتم تهيئة المثيل بالكامل.
ماذا يعني ذلك؟ تتم تهيئة الكائن في ثلاث خطوات ، ويمثله الرمز الزائف التالي:
الذاكرة = تخصيص () ؛ // 1. تخصيص مساحة الذاكرة للكائن ctorinstance (الذاكرة) ؛ // 2. تهيئة مثيل الكائن = الذاكرة ؛ // 3. اضبط مساحة ذاكرة الكائن مشيرًا إلى الكائن
نظرًا لأن الخطوة 2 والخطوة 3 تحتاج إلى الاعتماد على الخطوة 1 ، والخطوة 2 والخطوة 3 لا تتمتعان بالاعتماد ، فمن المحتمل أن يخضع هذان البيانان إلى إعادة ترتيب التعليمات ، أي ، أو من الممكن أن يتم تنفيذ الخطوة 3 قبل الخطوة 2. في هذه الحالة ، يتم تنفيذ الخطوة 3 ، ولكن الخطوة 2 لم يتم إعدامها بعد ، هذا هو لم يتم تنفيذ الحالة على سبيل المثال. الآن فقط ، قضاة الخيط 2 بأن المثيل ليس فارغًا ، لذلك يعيد مثيل المثيل مباشرة. ومع ذلك ، في هذا الوقت ، مثيل هو في الواقع كائن غير مكتمل ، لذلك ستكون هناك مشاكل عند استخدامه.
يعني استخدام الكلمة الرئيسية المتطايرة استخدام مبدأ "كتابة متغير تم تعديله بواسطة متقلبة ، قبل قراءة المتغير في أي وقت لاحق" يتوافق مع عملية التهيئة أعلاه. الخطوتين 2 و 3 على حد سواء مثالان الكتابة ، لذلك يجب أن يحدث لاحقًا عند قراءة الحالات ، أي لن يكون هناك إمكانية لإعادة مثيل لم يتم تهيئته بالكامل.
يتم JVM الأساسي من خلال شيء يسمى "حاجز الذاكرة". حاجز الذاكرة ، المعروف أيضًا باسم سياج الذاكرة ، هو مجموعة من تعليمات المعالج المستخدمة لتنفيذ قيود متسلسلة على عمليات الذاكرة.
في النهاية
من خلال الكلمة الرئيسية المتطايرة ، تعلمنا عن الرؤية والنظام في البرمجة المتزامنة ، وهو بالطبع مجرد فهم بسيط. من أجل فهم أعمق ، عليك الاعتماد على زملائك في الدراسة لدراستها بنفسك.
المقالات ذات الصلة
ما هي أقفال تدور CAS التي نتحدث عنها
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.