سؤال

اليوم جئت عبر هذا السؤال:

لديك رمز

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

إذا worker سيتم استدعاء من اثنين من المواضيع المختلفة، ما قيمة الإرادة counter لديك بعد الانتهاء من كل منهم؟

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

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

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

المحلول

خصيصا ل X86، وفيما يتعلق بمثالك: counter++, ، هناك عدد من الطرق التي يمكن جمعها. المثال الأكثر تافهة هو:

inc counter

هذا يترجم إلى العمليات الصغيرة التالية:

  • حمل counter إلى سجل مخفي على وحدة المعالجة المركزية
  • زيادة السجل
  • تخزين السجل المحدث في counter

هذا هو نفسه في الأساس:

mov eax, counter
inc eax
mov counter, eax

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

إذا كنت ترغب في ضمان أن هذا inc هو الذرية، واستخدام lock اختصار:

lock inc counter

lock ضمانات أن لا أحد يستطيع التحديث counter بين الحمل والمتجر.


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

نصائح أخرى

الجواب هو، فإنه يعتمد!

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

ومع ذلك، فإن السؤال يتلخص هو تعليم الجهاز واحد؟

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

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

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

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

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

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

استخدم تقنيات المزامنة المناسبة بدلا من ذلك، تعتمد على اللغة التي ترميز فيها.

  1. عمليات الزيادة / التناقص على متغيرات عدد صحيح 32 بت أو أقل على معالج واحد 32 بت مع وجود تكنولوجيا خيوط فرط ذرية.
  2. على معالج مع تقنية خيوط فرط أو على نظام متعدد المعالجات، لا يتم ضمان عمليات الزيادة / الانقراض ليتم تنفيذها Atomicaly.

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

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

باختصار، لا تعتمد على أنها ذرية ما لم يتم تحديدها من خلال مواصفات اللغة وأن التحويل البرمجي الذي تستخدمه يدعم المواصفات.

لا لا يمكنك تحمل ذلك. ما لم ينص بوضوح في مواصفات مترجم. وعلاوة على ذلك، لا يمكن لأحد أن يضمن أن تعورات مجمع واحد واحدة ذرية. في الممارسة العملية، يتم ترجمة كل تعليمات مجمع إلى عدد من عمليات Microcode - UOPS.
أيضا قضية شرط السباق مقرونة بإحكام بنموذج الذاكرة (الاتساق والتسلسل والاستتماس والإصدار وما إلى ذلك)، لكل واحد الإجابة والنتيجة قد تكون مختلفة.

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

قد لا تكون إجابة فعلية على سؤالك، ولكن (على افتراض أن هذا هو C #، أو لغة .NET أخرى) إذا كنت تريد counter++ أن تكون حقا ذرية متعددة الخيوط، يمكنك استخدام System.Threading.Interlocked.Increment(counter).

انظر إجابات أخرى للحصول على معلومات فعلية عن العديد من الطرق المختلفة لماذا / كيف counter++ لا يمكن أن تكون ذرية. ؛-)

في معظم الحالات، رقم. وبعد في الواقع، في X86، يمكنك إجراء التعليمات

push [address]

أي، في ج، سيكون شيئا مثل:

*stack-- = *address;

هذا يؤدي نقل ذاكرة اثنين في تعليمات واحدة.

هذا من المستحيل في الأساس القيام به في دورة ساعة واحدة، وليس أقلها واحد نقل الذاكرة غير ممكن أيضا في دورة واحدة!

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

لهذا الغرض، هناك حواجز الذاكرة (http://en.wikipedia.org/wiki/memory_barrier.)

باختصار، في حين أن التعليمات الذرية تكفي على Intel (مع بادئات القفل ذات الصلة)، يجب القيام بالمزيد على غير إنتيل، لأن الذاكرة I / O قد لا تكون بنفس الترتيب.

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

(لاحظ أن أنظمة المعالجات المتعددة (NOT MultiCore) على X86 يبدو أن تحتاج أيضا إلى حواجز الذاكرة، على الأقل في وضع 64 بت.

أعتقد أنك ستحصل على حالة سباق عند الوصول.

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

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