سؤال

توفر بعض اللغات أ volatile المعدل الذي يوصف بأنه يؤدي "قراءة حاجز الذاكرة" قبل قراءة الذاكرة التي تدعم المتغير.

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

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

هل كانت مفيدة؟

المحلول

هناك حواجز قراءة وحواجز الكتابة. الحصول على الحواجز والحواجز إطلاق. وأكثر من ذلك (IO مقابل الذاكرة ، إلخ).

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

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

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

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

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

ما أنواع الكتل؟ - اكتساب و/أو كتل الإفراج.

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

الإصدار-على سبيل المثال ، سوف يقوم بإطلاق الكتابة (X ، 5) بتدفق (أو علامة) قائمة الانتظار أولاً ، ثم إضافة طلب الكتابة إلى الكتابة. لذا ، لن يتم إعادة ترتيب الكتابة في وقت سابق بعد x = 5 ، ولكن لاحظ أنه يمكن إعادة ترتيب الكتابة لاحقًا قبل x = 5.

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

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

يطبق الحاجز الكامل (أو السياج الكامل) كلاً من اكتساب وإصدار - أي لا إعادة ترتيب.

عادةً لبرمجة Lockfree ، أو C# أو Java 'Folatile' ، ما تريده/تحتاجه هو القراءة والكتابة.

بمعنى آخر

void threadA()
{
   foo->x = 10;
   foo->y = 11;
   foo->z = 12;
   write_release(foo->ready, true);
   bar = 13;
}
void threadB()
{
   w = some_global;
   ready = read_acquire(foo->ready);
   if (ready)
   {
      q = w * foo->x * foo->y * foo->z;
   }
   else
       calculate_pi();
}

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

بعد الانتهاء من threada () في كتابة foo ، يحتاج إلى كتابة foo-> جاهز أخيرًا ، أخيرًا ، قد ترى مؤشرات الترابط الأخرى foo-> جاهزة مبكرًا والحصول على القيم الخاطئة لـ x/y/z. لذلك نحن نستخدم أ write_release على Foo-> جاهز ، والتي ، كما ذكر أعلاه ، "قم بتطهير" قائمة انتظار الكتابة بشكل فعال (التأكد من ارتكاب x ، y ، z) ثم يضيف طلب Ready = true إلى قائمة الانتظار. ثم يضيف الشريط = 13 طلب. لاحظ أنه نظرًا لأننا استخدمنا فقط حاجز الإصدار (ليس كاملًا) ، قد يتم كتابة شريط = 13 قبل جاهز. لكننا لا نهتم! أي أننا نفترض أن شريط لا يغير البيانات المشتركة.

الآن يجب أن يعرف Threadb () أنه عندما نقول "جاهزًا" ، فإننا نعني حقًا جاهزًا. لذلك نحن نفعل read_acquire(foo->ready). تتم إضافة هذه القراءة إلى قائمة انتظار القراءة ، ثم يتم مسح قائمة الانتظار. لاحظ أن w = some_global قد لا يزال أيضا في قائمة الانتظار. لذلك يمكن قراءة Foo-> جاهزة قبل some_global. لكن مرة أخرى ، نحن لا نهتم ، لأنها ليست جزءًا من البيانات المهمة التي نكون حذرين للغاية. ما نهتم به هو foo-> x/y/z. لذلك يتم إضافتها إلى قائمة انتظار القراءة بعد الحصول على Flush/Marker ، مما يضمن قراءة فقط بعد قراءة Foo-> جاهزة.

لاحظ أيضًا أن هذا هو عادة نفس الحواجز المستخدمة في قفل وفتح Mutex/Criticsection/etc. (أي الحصول على Lock () ، relement on unlock ()).

لذا،

  • أنا متأكد تمامًا من أن هذا (أي الحصول/الإصدار) هو بالضبط ما تقوله مستندات MS يحدث للمتغيرات "المتطايرة" في C# (واختياريًا لـ MS C ++ ، ولكن هذا غير قياسي). يرى http://msdn.microsoft.com/en-us/library/aa645755(vs.71).aspx بما في ذلك "القراءة المتطايرة" اكتسبت دلالات "؛ أي ، من الضمان أن يحدث قبل أي إشارات إلى الذاكرة التي تحدث بعدها ..."

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

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

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

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

نصائح أخرى

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

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

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

هنا هو أحد الاشعال على حواجز ذاكرة وحدة المعالجة المركزية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top