سؤال

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

بلدي الحالي هو رمز على النحو التالي ، يفترض x86 و دول مجلس التعاون الخليجي:

volatile int exclusion = 0;

void lock() {
    while (__sync_lock_test_and_set(&exclusion, 1)) {
        // Do nothing. This GCC builtin instruction
        // ensures memory barrier.
    }
}

void unlock() {
    __sync_synchronize(); // Memory barrier.
    exclusion = 0;
}

لذلك أنا أتساءل:

  • هل هذا الكود صحيح ؟ هل صحيح المتبادلة الإقصاء ؟
  • هل يعمل على جميع x86 أنظمة التشغيل?
  • هل يعمل على x86_64 أيضا ؟ على جميع أنظمة التشغيل?
  • هو الأمثل ؟
    • رأيت تدور قفل تطبيقات باستخدام قارن و مبادلة ولكن لست متأكدا أيهما أفضل.
    • وفقا دول مجلس التعاون الخليجي الذرية builtins الوثائق (http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html) هناك أيضا __sync_lock_release.أنا لست خبيرا في الذاكرة الحواجز لذلك أنا لست متأكدا ما إذا كان على ما يرام بالنسبة لي استخدام هذا بدلا من ذلك __sync_synchronize.
    • أنا الأمثل في الحالة التي لا يوجد خلاف.

لا يهمني في كل عن الخلاف.قد يكون هناك 1 ، 2 ربما المواضيع الأخرى تحاول قفل تدور قفل مرة واحدة كل بضع أيام.

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

المحلول

لذلك أنا أتساءل:

* Is it correct?

في السياق المذكور ، أود أن أقول نعم.

* Is it optimal?

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

  • أتوقع حلقة نفايات على الفشل حيث لا تحاول الوصول إلى كلمة القفل.

  • يجب أن يكون استخدام حاجز كامل في إلغاء القفل لا يحتاج إلا إلى إصدار دلالات (لهذا السبب تستخدم __sync_lock_release ، بحيث تحصل على st1.rel على itanium بدلاً من MF ، أو lwsync على powerpc ، ...). إذا كنت تهتم حقًا فقط بـ x86 أو x86_64 ، فإن أنواع الحواجز المستخدمة هنا أو لا تهم كثيرًا (ولكن إذا كنت تقفز إلى itanium من Intel لمنفذ HP-IPF ، فلن ترغب في ذلك).

  • ليس لديك تعليمات الإيقاف المؤقت () التي عادة ما تضعها قبل حلقة النفايات الخاصة بك.

  • عندما يكون هناك خلاف تريده شيئا ما, ، semop ، أو حتى نوم غبي في اليأس. إذا كنت بحاجة حقًا إلى الأداء الذي يشتريه هذا ، فمن المحتمل أن يكون اقتراح Futex جيدًا. إذا كنت بحاجة إلى الأداء ، فإن هذا يشتري لك سيئًا بما يكفي الحفاظ هذا الرمز لديك الكثير من الأبحاث للقيام به.

لاحظ أنه كان هناك تعليق يقول أن حاجز الإصدار لم يكن مطلوبًا. هذا ليس صحيحًا حتى في x86 لأن حاجز الإصدار يعمل أيضًا كتعليمات للمترجم لعدم خلط الذاكرة الأخرى حول "الحاجز". يشبه إلى حد كبير ما ستحصل عليه إذا كنت تستخدم ASM ("" ::: "ذاكرة" ).

* on compare and swap

في x86 ، سيقوم Sync_lock_test_and_set بتخطيط تعليمات XCHG التي تحتوي على بادئة قفل ضمنية. من المؤكد أن الكود الأكثر إحكاما تم إنشاؤه (esp. إذا كنت تستخدم بايت لـ "Word Lock" بدلاً من int) ، ولكن ليس أقل صحة مما إذا كنت تستخدم قفل CMPXCHG. يمكن استخدام استخدام المقارنة والمبادلة لـ Fancier Algorthims (مثل وضع مؤشر غير صفري إلى البيانات الوصفية لأول "نادل" في الكلمة على الفشل).

نصائح أخرى

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

void lock(volatile int *exclusion)
{
    while (__sync_lock_test_and_set(exclusion, 1))
        while (*exclusion)
            ;
}

ردا على أسئلتك:

  1. تبدو جيدة بالنسبة لي
  2. على افتراض أن نظام التشغيل يدعم GCC (و GCC لديه الوظائف المنفذة) ؛ هذا يجب أن يعمل على جميع أنظمة التشغيل x86. تشير وثائق دول مجلس التعاون الخليجي إلى أنه سيتم إنتاج تحذير إذا لم يتم دعمهم على منصة معينة.
  3. لا يوجد شيء x86-64 محدد هنا ، لذلك لا أرى لماذا لا. يمكن توسيع هذا لتغطية أي الهندسة المعمارية التي تدعمها مجلس التعاون الخليجي ، ولكن ربما هناك طرق مثالية لتحقيق ذلك على بنية غير x86.
  4. قد تكون أفضل قليلاً مع الاستخدام __sync_lock_release() في ال unlock() قضية؛ لأن هذا سيؤدي إلى تقليل القفل ويضيف حاجز الذاكرة في عملية واحدة. ومع ذلك ، على افتراض أن تأكيدك على أنه نادراً ما يكون هناك خلاف ؛ تبدو جيدة بالنسبة لي.

إذا كنت على إصدار حديث من Linux ، فقد تتمكن من استخدام ملف FUTEX - "mutex fast userpace":

لن يستخدم القفل المستند إلى FUTEX المبرمج بشكل صحيح مكالمات النظام إلا عند التنافس على القفل

في القضية التي لا تم اختبارها ، والتي تحاول تحسينها من خلال Spinlock ، سيتصرف Futex تمامًا مثل الدوران ، دون الحاجة إلى syscall kernel. إذا تم التنافس على القفل ، فإن الانتظار يحدث في النواة دون انتظار مشغول.

أتساءل عما إذا كان تطبيق CAS التالي هو التنفيذ الصحيح على x86_64. إنه أسرع مرتين تقريبًا على جهاز الكمبيوتر المحمول i7 x920 (Fedora 13 x86_64 ، GCC 4.4.5).

inline void lock(volatile int *locked) {
    while (__sync_val_compare_and_swap(locked, 0, 1));
    asm volatile("lfence" ::: "memory");
}
inline void unlock(volatile int *locked) {
    *locked=0;
    asm volatile("sfence" ::: "memory");
}

لا يمكنني التعليق على الصواب ، لكن عنوان سؤالك أثار العلم الأحمر قبل أن أقرأ جسم الأسئلة. من الصعب للغاية ضمان صحة أولي التزامن ... إذا كان ذلك ممكنًا ، فأنت أفضل حالًا باستخدام مكتبة مصممة جيدًا/محافظة عليها ، ربما pthreads أو Boost :: Thread.

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

فتح الخاص بك الإجراء لا يحتاج الذاكرة حاجز;الإحالة إلى الاستبعاد الذرية طالما أنه dword الانحياز على x86.

في الحالة المحددة المتمثلة في x86 (32/64) لا أعتقد أنك بحاجة إلى سياج ذاكرة على الإطلاق في رمز فتح. X86 لا يقوم بأي إعادة ترتيب ، باستثناء أن المتاجر يتم وضعها أولاً في المخزن المؤقت للمتجر ، وبالتالي يمكن تأخيرها للمرئيات للخيوط الأخرى. وسيقرأ الخيط الذي يقوم بمتجر ثم يقرأ من نفس المتغير من المخزن المؤقت المتجر إذا لم يتم بعد حدوثه بعد إلى الذاكرة. لذلك كل ما تحتاجه هو asm بيان لمنع إعادة ترتيب البرمجيات. يمكنك تواجد خطر إحدى الخيط الذي يحمل القفل لفترة أطول قليلاً من الضرورة من منظور المواضيع الأخرى ، ولكن إذا كنت لا تهتم بالزعم الذي لا يهم. حقيقة، pthread_spin_unlock يتم تنفيذها على هذا النحو على نظامي (Linux X86_64).

ينفذ نظامي أيضًا pthread_spin_lock استخدام lock decl lockvar; jne spinloop; بدلا من استخدام xchg (وهو ما __sync_lock_test_and_set يستخدم) ، لكنني لا أعرف ما إذا كان هناك بالفعل فرق في الأداء.

هناك بعض الافتراضات الخاطئة.

أولاً ، يكون Spinlock منطقيًا فقط إذا تم قفل Ressource على وحدة المعالجة المركزية الأخرى. إذا تم قفل Ressource على نفس وحدة المعالجة المركزية (وهو ما هو الحال دائمًا على أنظمة Uniprocessor) ، فأنت بحاجة إلى الاسترخاء من أجل فتح Ressource. ستعمل رمزك الحالي على نظام Uniprocessor لأن Scheduler سيقوم بتبديل المهام التلقائية ، ولكنه مضيعة للريسورس.

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

ثانياً ، يكون قفل Mutex سريعًا (بأسرع ما يكون spinlock) عندما يتم فتحه. قفل Mutexes (وفتح) بطيء (بطيء جدًا) فقط إذا كان Mutex مغلقًا بالفعل.

لذلك ، في حالتك ، أقترح استخدام Mutexes.

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