مميزات - قدمت C++ 11 نموذج ذاكرة قياسي. ماذا تعني؟ وكيف سيؤثر على برمجة C++؟




مميزات لغة السي بلس بلس (4)

قدم C ++ 11 نموذج ذاكرة قياسي ، ولكن ماذا يعني ذلك بالضبط؟ وكيف سيؤثر على برمجة C ++؟

هذا المقال (من قبل غافين كلارك الذي يقتبس هيرب سوتر ) يقول ،

يعني نموذج الذاكرة أن كود C ++ يحتوي الآن على مكتبة قياسية لاستدعاء بغض النظر عمن قام بالتجميع وعلى النظام الأساسي الذي يعمل عليه. هناك طريقة قياسية للتحكم في كيفية اختلاف المواضيع المختلفة مع ذاكرة المعالج.

وقال سوتر "عندما تتحدث عن تقسيم [الكود] عبر نوى مختلفة في المعيار ، فإننا نتحدث عن نموذج الذاكرة. وسنقوم بتحسينه دون كسر الافتراضات التالية التي سيقوم الناس بإجرائها في الكود".

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

لذا ، ما أريد أن أعرفه أساساً هو ، مبرمجو C ++ الذين يستخدمون لتطوير تطبيقات متعددة الخيوط حتى من قبل ، فكيف يهم إذا كانت POSIX هي سلاسل عمليات ، أو سلاسل عمليات Windows ، أو سلاسل C ++ 11؟ ما هي المنافع؟ أريد أن أفهم التفاصيل منخفضة المستوى.

أنا أيضا الحصول على هذا الشعور أن نموذج ذاكرة C ++ 11 يرتبط بطريقة أو بأخرى بدعم C ++ 11 متعددة الخيوط ، كما غالبا ما أرى هذين معا. إذا كان كذلك ، كيف بالضبط؟ لماذا يجب أن تكون ذات صلة؟

بما أنني لا أعرف كيف يعمل internals من multi-threading وماذا يعني نموذج الذاكرة بشكل عام ، الرجاء مساعدتي في فهم هذه المفاهيم. :-)


أولا ، عليك أن تتعلم التفكير مثل محامي اللغة.

مواصفات C ++ لا يشير إلى أي مترجم معين أو نظام التشغيل أو CPU. إنه يشير إلى آلة مجردة هي تعميم للأنظمة الفعلية. في عالم محامي اللغة ، وظيفة المبرمج هي كتابة الكود للجهاز التجريدي. وظيفة المترجم هي تفعيل هذا الرمز على آلة ملموسة. من خلال الترميز الصارم للمواصفات ، يمكنك التأكد من أن الكود الخاص بك سيتم تجميع وتشغيل بدون تعديل على أي نظام مع مترجم C ++ متوافق ، سواء اليوم أو 50 سنة من الآن.

الجهاز المجرد في مواصفات C ++ 98 / C ++ 03 مترابطة المفرد بشكل أساسي. لذلك ، ليس من الممكن كتابة كود C ++ متعدد الخيوط "محمول بالكامل" فيما يتعلق بالمواصفات. ولا تشير المواصفات حتى إلى أي شيء حول ذرية أحمال الذاكرة والمخازن أو الترتيب الذي قد تحدث فيه الأحمال والمخازن ، ولا تمانع في أشياء مثل كائنات التماسيح.

بالطبع ، يمكنك كتابة رموز متعددة الخيوط في الممارسة لأنظمة خرسانة معينة - مثل pthreads أو Windows. ولكن لا توجد طريقة قياسية لكتابة تعليمة برمجية Multi-threaded لـ C ++ 98 / C ++ 03.

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

خذ بعين الاعتبار المثال التالي ، حيث يتم الوصول إلى زوج من المتغيرات العمومية بشكل متزامن بواسطة اثنين من مؤشرات الترابط:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

ما قد الناتج 2 الخيط؟

تحت C ++ 98 / C ++ 03 ، هذا ليس حتى السلوك غير المعرّف؛ السؤال نفسه لا معنى له لأن المعيار لا يتأمل أي شيء يسمى "الخيط".

تحت C ++ 11 ، تكون النتيجة سلوك غير محدد ، لأن الأحمال والمخازن لا يجب أن تكون ذرية بشكل عام. التي قد لا تبدو مثل الكثير من التحسن ... وفي حد ذاته ، ليس كذلك.

ولكن مع C ++ 11 ، يمكنك كتابة هذا:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

الآن أصبحت الأمور أكثر إثارة. بادئ ذي بدء ، يتم تعريف السلوك هنا. يمكن الآن قراءة مؤشر الترابط 2 0 0 (إذا كان يعمل قبل مؤشر الترابط 1) ، 37 17 (إذا كان يعمل بعد مؤشر ترابط 1) أو 0 17 (إذا كان يعمل بعد تعيين خيط 1 إلى x ولكن قبل تعيينها إلى y).

ما لا يمكن طباعته هو 37 0 ، لأن الوضع الافتراضي للأحمال / المخازن الذرية في C ++ 11 هو فرض الاتساق التتابعي . هذا يعني أن جميع الأحمال والمخازن يجب أن تكون "كما لو" ، فقد حدث بالترتيب الذي كتبته في كل موضوع ، في حين أن العمليات بين سلاسل الرسائل يمكن تشذيرها على الرغم من أن النظام يحبها. لذا فإن السلوك الافتراضي للذرات يوفر كل من الذرية وترتيب الأحمال والمخازن.

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

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

وكلما كانت وحدة المعالجة المركزية أكثر حداثة ، زادت احتمالية أن يكون هذا أسرع من المثال السابق.

أخيرًا ، إذا كنت تحتاج فقط إلى الاحتفاظ بأحمال ومخازن معينة بالترتيب ، فيمكنك كتابة:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

هذا يعيدنا إلى الأحمال والمخازن المطلوبة - لذلك لم يعد 37 0 مخرجًا محتملًا - ولكنه يفعل ذلك مع الحد الأدنى من الحمل. (في هذا المثال التافه ، تكون النتيجة هي نفس الاتساق التسلسلي الكامل ؛ في برنامج أكبر ، لن تكون كذلك).

بالطبع ، إذا كانت النواتج الوحيدة التي ترغب في مشاهدتها هي 0 0 أو 37 17 ، فيمكنك فقط التفاف كائن كتيب حول الرمز الأصلي. ولكن إذا كنت قد قرأت هذا الآن ، أراهن أنك تعرف بالفعل كيف يعمل هذا ، وهذه الإجابة هي بالفعل أطول مما كنت أقصد :-).

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

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

لمزيد من المعلومات عن هذه الأشياء ، راجع مشاركة المدونة هذه .


إذا كنت تستخدم كائنات جماعية لحماية جميع بياناتك ، فلا داعي للقلق. وقد قدمت دائما Mutexes ما يكفي من الضمانات وضوح الرؤية.

الآن ، إذا كنت تستخدم atomics ، أو خوارزميات خالية من التأمين ، تحتاج إلى التفكير في نموذج الذاكرة. يصف نموذج الذاكرة بدقة عندما توفر الذرات atomics ضمانات ترتيب وضوح ، ويوفر أسوارًا محمولة للضمانات المشفرة يدويًا.

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


سأعطي فقط القياس الذي أفهم نماذج تناسق الذاكرة (أو نماذج الذاكرة ، باختصار). مستوحاة من ورقة ليزلي لامبورت الأصلية "Time، Clocks، و Ordering of Events in a Distributed System" . التناظر هو مناسب وله أهمية أساسية ، ولكن قد يكون مبالغة للكثير من الناس. ومع ذلك ، آمل أن يوفر صورة ذهنية (تمثيل تصويري) تسهل التفكير في نماذج تناسق الذاكرة.

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

نقلا عن "A التمهيدي على تناسق الذاكرة وذاكرة التماسك"

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

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

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

[صورة من ويكيبيديا]

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

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

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

في نموذج الذاكرة C ++ 11 ، يتم استخدام آلية مماثلة (نموذج تماسك الإطلاق) للحصول على هذه العلاقات السببية المحلية .

لتوفير تعريف لاتساق الذاكرة ودافع للتخلي عن SC ، سوف أقتبس من "A Primer on Memory Consistency and Cache Coherence"

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

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

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

ولأن تماسك ذاكرة التخزين المؤقت وتناسق الذاكرة يتم الخلط بينهما أحيانًا ، فمن المفيد أيضًا الحصول على هذا الاقتباس:

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

واستمرارًا لصورتنا العقلية ، يتوافق معيار SWMR الثابت مع المتطلبات المادية التي تنص على وجود جسيم واحد على الأكثر في أي موقع ، ولكن يمكن أن يكون هناك عدد غير محدود من المراقبين في أي مكان.


هذا يعني أن المعيار يعرف الآن متعدد الخيوط ، ويحدد ما يحدث في سياق خيوط متعددة. بالطبع ، استخدم الناس تطبيقات مختلفة ، ولكن هذا مثل السؤال عن السبب في أننا يجب أن يكون لدينا std::string عندما نتمكن جميعًا من استخدام فئة string مدحرجة في المنزل.

عندما تتحدث عن سلاسل رسائل POSIX أو سلاسل عمليات Windows ، فإن هذا هو نوع من الوهم ، حيث أنك تتحدث بالفعل عن x86 threads ، حيث إنها وظيفة جهاز يتم تشغيلها بشكل متزامن. يوفر طراز ذاكرة C ++ 0x ضمانات ، سواء كنت على x86 ، أو ARM ، أو MIPS ، أو أي شيء آخر يمكنك التوصل إليه.





memory-model