المبادلة الذرية في GNU C ++
-
21-09-2019 - |
سؤال
أريد أن أتحقق من أن فهمي صحيح. هذا النوع من الأشياء أمر صعب ، لذا فأنا متأكد من أنني أفتقد شيئًا. لدي برنامج يتكون من موضوع في الوقت الفعلي وخيط وقت غير حقيقي. أريد أن يكون مؤشر ترابط غير RT قادرًا على تبديل مؤشر إلى الذاكرة المستخدمة بواسطة مؤشر ترابط RT.
من المستندات ، أفهم أن هذا يمكن إنجازه في g++
مع:
// global
Data *rt_data;
Data *swap_data(Data *new_data)
{
#ifdef __GNUC__
// Atomic pointer swap.
Data *old_d = __sync_lock_test_and_set(&rt_data, new_data);
#else
// Non-atomic, cross your fingers.
Data *old_d = rt_data;
rt_data = new_data;
#endif
return old_d;
}
هذا هو المكان الوحيد في البرنامج (بخلاف الإعداد الأولي) حيث rt_data
تم تعديله. متى rt_data
يستخدم في سياق الوقت الحقيقي ، يتم نسخه إلى مؤشر محلي. ل old_d
, ، في وقت لاحق عندما يكون من المؤكد أنه لا يتم استخدام الذاكرة القديمة ، سيتم تحريرها في الخيط غير RT. هل هذا صحيح؟ هل أحتاج volatile
في أى مكان؟ هل هناك بدايات التزامن أخرى يجب أن أتصل بها؟
بالمناسبة أقوم بذلك في C ++ ، على الرغم من أنني مهتم بما إذا كانت الإجابة تختلف عن C.
شكرا في وقت مبكر.
المحلول
عموما لا تستخدم volatile
عند كتابة الرمز المتزامن في C/C++
. دلالات volatile
قريبة جدًا مما تريده حتى أنه مغري ولكن في النهاية متقلبة ليس كافي. للأسف Java/C# volatile != C/C++ volatile
. Herb Sutter لديه عظيم مقالة - سلعة شرح الفوضى المربكة.
ما تريده حقًا هو سياج ذاكرة. __sync_lock_test_and_set
يوفر المبارزة لك.
ستحتاج أيضًا إلى سياج ذاكرة عند نسخ (تحميل) مؤشر RT_DATA إلى نسختك المحلية.
قفل البرمجة المجانية صعبة. إذا كنت على استعداد لاستخدام ملحقات C ++ 0x من GCC ، فسيكون الأمر أسهل قليلاً:
#include <cstdatomic>
std::atomic<Data*> rt_data;
Data* swap_data( Data* new_data )
{
Data* old_data = rt_data.exchange(new_data);
assert( old_data != new_data );
return old_data;
}
void use_data( )
{
Data* local = rt_data.load();
/* ... */
}
نصائح أخرى
تحديث: هذه الإجابة غير صحيحة ، لأنني أفتقد حقيقة ذلك volatile
يضمن الوصول إلى volatile
لم يتم إعادة ترتيب المتغيرات ، ولكن لا توفر هذه الضمانات فيما يتعلق بغير غيرهاvolatile
الوصول والتلاعب. يوفر سياج الذاكرة مثل هذه الضمانات ، وهو ضروري لهذا التطبيق. إجابتي الأصلية أدناه ، لكن لا تتصرف عليها. يرى هذا الجواب للحصول على تفسير جيد في الحفرة في فهمي الذي أدى إلى استجابة غير صحيحة التالية.
الإجابة الأصلية:
نعم ، أنت بحاجة volatile
على الخاص بك rt_data
تصريح؛ في أي وقت يمكن تعديل متغير خارج تدفق التحكم في خيط الوصول إليه ، يجب إعلانه volatile
. بينما قد تكون قادرًا على الفرار بدون volatile
بما أنك تناسخ إلى مؤشر محلي ، volatile
على الأقل يساعد في الوثائق ويمنع أيضًا بعض تحسينات البرامج التحويم التي يمكن أن تسبب مشاكل. النظر في المثال التالي ، المعتمد من DDJ:
volatile int a;
int b;
a = 1;
b = a;
إذا كان ذلك ممكنًا a
لتغيير قيمتها بين a=1
و b=a
, ، من ثم a
يجب إعلانه volatile
(ما لم يكن ، بالطبع ، تعيين قيمة خارج تاريخ b
مقبولة). Multithreading ، وخاصة مع البدائية الذرية ، تشكل مثل هذا الموقف. يتم تشغيل الموقف أيضًا مع المتغيرات التي تم تعديلها بواسطة معالجات الإشارات والمتغيرات المعينة إلى مواقع الذاكرة الفردية (مثل سجلات I/O للأجهزة). أنظر أيضا هذا السؤال.
خلاف ذلك ، يبدو الأمر جيدًا بالنسبة لي.
في C ، ربما أستخدم البدائل الذرية التي توفرها جيب لهذا. سوف يستخدمون عملية ذرية حيثما كان ذلك متاحًا ويعودون إلى التنفيذ البطيء غير الصحيح القائم على Mutex إذا لم تكن العمليات الذرية متوفرة. قد يوفر Boost شيئًا مشابهًا لـ C ++.