سؤال

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

الموضوع 1:

while(1) {
    if (dataUpdated)
        updateScreen();
    doSomethingElse();
}

الموضوع 2:

while(1) {
    if (doSomething())
        dataUpdated = TRUE;
}

هل يقوم مترجم مثل gcc بتحسين هذا الكود بطريقة لا تتحقق من القيمة العالمية، مع الأخذ في الاعتبار فقط قيمته في وقت الترجمة (لأنه لا يتغير أبدًا في نفس الموضوع)؟

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

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

المحلول

نعم.لا.ربما.

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

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

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

نصائح أخرى

فيما يلي مثال يستخدم متغيرات حالة التعزيز:

bool _updated=false;
boost::mutex _access;
boost::condition _condition;

bool updated()
{
  return _updated;
}

void thread1()
{
  boost::mutex::scoped_lock lock(_access);
  while (true)
  {
    boost::xtime xt;
    boost::xtime_get(&xt, boost::TIME_UTC);
    // note that the second parameter to timed_wait is a predicate function that is called - not the address of a variable to check
    if (_condition.timed_wait(lock, &updated, xt))
      updateScreen();
    doSomethingElse();
  }
}

void thread2()
{
  while(true)
  {
    if (doSomething())
      _updated=true;
  }
}

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

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

استخدم ال متقلب الكلمة الأساسية لتلميح المترجم بأن القيمة يمكن أن تتغير في أي وقت.

volatile int myInteger;

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

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

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

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

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

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

سيستخدم الحل الخاص بك وحدة المعالجة المركزية بنسبة 100%، من بين مشكلات أخرى.جوجل عن "متغير الشرط".

وأشار كريس جيستر يونج إلى أن:

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

وبما أن الأمر كذلك، فإن الإجابة الحقيقية الوحيدة هي تنفيذ نظام المزامنة، أليس كذلك؟

استخدم ال متقلب الكلمة الأساسية لتلميح المترجم بأن القيمة يمكن أن تتغير في أي وقت.

volatile int myInteger;

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

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

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

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

الدفع خطر خط الأنابيب على ويكيبيديا أو ابحث في جوجل عن "إعادة ترتيب تعليمات المترجم"

وكما قال آخرون volatile الكلمة الرئيسية هي صديقك.:-)

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

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

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

مع كل ما قيل قد تجد نتائج أفضل (كما ألمح إليه جيف) من خلال استخدام الإشارة أو متغير الشرط.

هذا تعتبر مقدمة معقولة للموضوع.

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