سؤال

لنفترض أن الفصل لديه public int counter الحقل الذي يتم الوصول إليه بواسطة سلاسل رسائل متعددة.هذا int لا يزيد أو ينقص إلا

لزيادة هذا المجال، ما هو النهج الذي ينبغي استخدامه، ولماذا؟

  • lock(this.locker) this.counter++;,
  • Interlocked.Increment(ref this.counter);,
  • تغيير معدّل الوصول لـ counter ل public volatile.

والآن بعد أن اكتشفت volatile, ، لقد قمت بإزالة الكثير lock التصريحات واستخدامها Interlocked.ولكن هل هناك سبب لعدم القيام بذلك؟

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

المحلول

الأسوأ (لن ينجح في الواقع)

تغيير معدّل الوصول لـ counter ل public volatile

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

اذا كانت لا volatile, ، ووحدة المعالجة المركزية A تزيد قيمة، فإن وحدة المعالجة المركزية B قد لا ترى هذه القيمة المتزايدة فعليًا إلا بعد مرور بعض الوقت، مما قد يسبب مشاكل.

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

ثاني أفضل:

lock(this.locker) this.counter++;

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

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

أفضل

Interlocked.Increment(ref this.counter);

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

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

ملاحظات متشابكة:

  1. تعد الطرق المتشابكة آمنة حاليًا على أي عدد من المعالجات المركزية أو وحدات المعالجة المركزية.
  2. تطبق الأساليب المتشابكة سياجًا كاملاً حول التعليمات التي تنفذها، لذلك لا تحدث إعادة ترتيب.
  3. الأساليب المتشابكة لا تحتاج أو حتى لا تدعم الوصول إلى مجال متقلب, حيث يتم وضع نصف سياج متطاير حول العمليات في حقل معين ويتم التشابك باستخدام السياج الكامل.

هامش:ما هو المتقلب هو في الواقع جيد ل.

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

لو queueLength ليس متطايرًا، فقد يكتب الخيط A خمس مرات، ولكن قد يرى الخيط B أن عمليات الكتابة هذه متأخرة (أو حتى من المحتمل أن تكون بترتيب خاطئ).

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

نصائح أخرى

يحرر: كما هو مذكور في التعليقات، يسعدني استخدام هذه الأيام Interlocked لحالات أ متغير واحد حيث ه بوضوح تمام.عندما يصبح الأمر أكثر تعقيدًا، سأظل أعود إلى القفل...

استخدام volatile لن يساعد عندما تحتاج إلى الزيادة - لأن القراءة والكتابة هما تعليمات منفصلة.يمكن لموضوع آخر تغيير القيمة بعد القراءة ولكن قبل الرد.

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

"volatile"لا يحل محل Interlocked.Increment!إنه يتأكد فقط من عدم تخزين المتغير مؤقتًا، ولكن استخدامه مباشرة.

تتطلب زيادة المتغير في الواقع ثلاث عمليات:

  1. يقرأ
  2. زيادة راتب
  3. يكتب

Interlocked.Increment ينفذ الأجزاء الثلاثة كعملية ذرية واحدة.

إما القفل أو الزيادة المتشابكة هو ما تبحث عنه.

من المؤكد أن Volatile ليس ما تبحث عنه - فهو ببساطة يخبر المترجم بالتعامل مع المتغير على أنه يتغير دائمًا حتى لو كان مسار الكود الحالي يسمح للمترجم بتحسين القراءة من الذاكرة بطريقة أخرى.

على سبيل المثال

while (m_Var)
{ }

إذا تم تعيين m_Var على false في مؤشر ترابط آخر ولكن لم يتم الإعلان عنه على أنه متطاير، فإن المترجم حر في جعله حلقة لا نهائية (ولكن هذا لا يعني أنه سيفعل ذلك دائمًا) عن طريق جعله يتحقق من سجل وحدة المعالجة المركزية (على سبيل المثال.EAX لأن هذا هو ما تم جلب m_Var إليه منذ البداية) بدلاً من إصدار قراءة أخرى لموقع ذاكرة m_Var (قد يتم تخزين هذا مؤقتًا - لا نعرف ولا نهتم وهذا هو الهدف من تماسك ذاكرة التخزين المؤقت لـ x86 /x64).جميع المشاركات السابقة التي نشرها الآخرون الذين ذكروا إعادة ترتيب التعليمات تظهر ببساطة أنهم لا يفهمون بنيات x86/x64.متقلبة لا لا قم بإصدار حواجز القراءة/الكتابة كما هو موضح في المنشورات السابقة التي تقول "إنها تمنع إعادة الترتيب".في الواقع، بفضل بروتوكول MESI مرة أخرى، نضمن أن النتيجة التي نقرأها هي نفسها دائمًا عبر وحدات المعالجة المركزية بغض النظر عما إذا كانت النتائج الفعلية قد تم سحبها إلى الذاكرة الفعلية أو ببساطة موجودة في ذاكرة التخزين المؤقت لوحدة المعالجة المركزية المحلية.لن أخوض كثيرًا في تفاصيل هذا الأمر ولكن كن مطمئنًا أنه إذا حدث خطأ ما، فمن المحتمل أن تقوم Intel/AMD بإصدار استدعاء للمعالج!وهذا يعني أيضًا أنه لا يتعين علينا الاهتمام بالتنفيذ خارج الترتيب وما إلى ذلك.النتائج مضمونة دائمًا للتقاعد بالترتيب - وإلا فإننا محشوون!

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

مع volatile، سينتهي بك الأمر بتعليمة واحدة فقط (بافتراض أن JIT فعال كما ينبغي) - inc dword ptr [m_Var].ومع ذلك، فإن المعالج (cpuA) لا يطلب الملكية الحصرية لخط ذاكرة التخزين المؤقت أثناء قيامه بكل ما فعله مع الإصدار المتشابك.كما يمكنك أن تتخيل، هذا يعني أن المعالجات الأخرى يمكنها كتابة قيمة محدثة مرة أخرى إلى m_Var بعد قراءتها بواسطة cpuA.لذا بدلًا من زيادة القيمة مرتين الآن، سينتهي بك الأمر بزيادة القيمة مرة واحدة فقط.

نأمل أن يكون هذا واضحا هذه القضية.

لمزيد من المعلومات، راجع "فهم تأثير تقنيات القفل المنخفض في التطبيقات متعددة الخيوط" - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

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

ص.أفترض أن الهدف هو x86/x64 وليس IA64 (يحتوي على نموذج ذاكرة مختلف).لاحظ أن مواصفات ECMA الخاصة بشركة Microsoft معيبة من حيث أنها تحدد نموذج الذاكرة الأضعف بدلاً من النموذج الأقوى (من الأفضل دائمًا تحديد نموذج الذاكرة الأقوى بحيث يكون متسقًا عبر الأنظمة الأساسية - وإلا فسيتم تشغيل التعليمات البرمجية على مدار 24 ساعة طوال أيام الأسبوع على نظام التشغيل x86/ قد لا يعمل الإصدار x64 على الإطلاق على IA64 على الرغم من أن Intel قد طبقت نموذج ذاكرة قوي مماثل لـ IA64) - اعترفت Microsoft بذلك بنفسها - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx.

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

أود أن أقول إنه يجب عليك دائمًا تفضيله على القفل والزيادة.

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

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

http://www.ddj.com/hpc-high-performance-computing/210604448

يعمل lock(...)‎، ولكنه قد يحظر سلسلة رسائل، وقد يتسبب في حالة توقف تام إذا كان رمز آخر يستخدم نفس الأقفال بطريقة غير متوافقة.

Interlocked.* هي الطريقة الصحيحة للقيام بذلك ...أقل بكثير من الحمل لأن وحدات المعالجة المركزية الحديثة تدعم هذا باعتباره بدائيًا.

متقلبة من تلقاء نفسها ليست صحيحة.من الممكن أن يتعارض مؤشر الترابط الذي يحاول استرداد قيمة معدلة ثم إعادة كتابتها مع مؤشر ترابط آخر يفعل نفس الشيء.

لقد أجريت بعض الاختبارات لأرى كيف تعمل النظرية فعليًا: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html.كان اختباري أكثر تركيزًا على CompareExchnage لكن نتيجة Increment مشابهة.المتشابكة ليست ضرورية بشكل أسرع في بيئة وحدات المعالجة المركزية المتعددة.فيما يلي نتيجة اختبار Increment على خادم وحدة المعالجة المركزية (CPU) الذي يبلغ عمره عامين.ضع في اعتبارك أن الاختبار يتضمن أيضًا القراءة الآمنة بعد الزيادة، وهو أمر معتاد في العالم الحقيقي.

D:\>InterlockVsMonitor.exe 16
Using 16 threads:
          InterlockAtomic.RunIncrement         (ns):   8355 Average,   8302 Minimal,   8409 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):   7077 Average,   6843 Minimal,   7243 Maxmial

D:\>InterlockVsMonitor.exe 4
Using 4 threads:
          InterlockAtomic.RunIncrement         (ns):   4319 Average,   4319 Minimal,   4321 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):    933 Average,    802 Minimal,   1018 Maxmial

أود أن أضيف إلى ما ورد في الإجابات الأخرى الفرق بينهما volatile, Interlocked, ، و lock:

يمكن تطبيق الكلمة الأساسية المتقلبة على الحقول من هذه الأنواع:

  • أنواع المراجع.
  • أنواع المؤشر (في سياق غير آمن).لاحظ أنه على الرغم من أن المؤشر نفسه يمكن أن يكون متطايرًا، إلا أن الكائن الذي يشير إليه لا يمكن أن يكون متطايرًا.
  • أنواع بسيطة مثل sbyte, byte, short, ushort, int, uint, char, float, ، و bool.
  • نوع التعداد مع أحد الأنواع الأساسية التالية: byte, sbyte, short, قصير, int, ، أو uint.
  • معلمات النوع العامة المعروفة بأنها أنواع مرجعية.
  • IntPtr و UIntPtr.

أنواع أخرى, ، مشتمل double و long, Interlocked أعضاء الفصل أو حماية الوصول باستخدامlock إفادة.

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