Proper maintenance and care of multi-threading locks

تُستخدم الاستراتيجيات التالية لضمان أن يكون الكود خاليًا من التوقف (عادةً من خلال معالجة الشرط الرابع من كوفمان: الانتظار الدائري).

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

Locks

فيما يلي جميع الأقفال الموجودة في النظام والآليات لاستخدامها التي تتجنب إمكانية حدوث حالات الجمود (لا يُسمح بخوارزمية النعامة هنا):

الأقفال التالية هي بالتأكيد أقفال ورقية (المستوى 1)، ويجب ألا تحاول الحصول على أي قفل آخر:

  • نقطة الأمان

    لاحظ أن هذا القفل يتم الحصول عليه ضمنيًا بواسطة JL_LOCK و JL_UNLOCK. استخدم المتغيرات _NOGC لتجنب ذلك لأقفال المستوى 1.

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

  • خريطة مشتركة

  • نهائيات

  • pagealloc

  • gcpermlock

  • flisp

  • jlinstackwalk (ويندوز 32)

  • ResourcePool<?>::mutex

  • RLST_mutex

  • llvmطباعةالمؤشر

  • jlمغلقتدفق::قفل

  • debuginfo_asyncsafe

  • استنتاجتوقيتقفل

  • ExecutionEngine::SessionLock

    flisp نفسه آمن بالفعل للخيوط، هذا القفل يحمي فقط مجموعة jl_ast_context_list_t بنفس الطريقة، بينما mutexes في ResourcePool<?> تحمي فقط مجموعة الموارد المرتبطة بها.

الآتي هو قفل ورقة (المستوى 2)، ويكتسب فقط أقفال المستوى 1 (نقطة الأمان) داخليًا:

  • globalrootslock
  • وحدة->قفل
  • JLDebuginfoPlugin::PluginMutex
  • newlyinferredmutex

الآتي هو قفل من المستوى 3، والذي يمكنه فقط الحصول على أقفال من المستوى 1 أو المستوى 2 داخليًا:

  • طريقة->قفل الكتابة
  • typecache

الآتي هو قفل من المستوى 4، والذي يمكنه فقط التكرار للحصول على أقفال من المستوى 1 أو 2 أو 3:

  • MethodTable->قفل_كتابة

لا يمكن استدعاء أي كود جوليا أثناء الاحتفاظ بقفل فوق هذه النقطة.

orc::ThreadSafeContext (TSCtx) الأقفال تحتل مكانة خاصة في تسلسل الأقفال. يتم استخدامها لحماية الحالة العالمية غير الآمنة للخيوط في LLVM، ولكن قد يكون هناك عدد غير محدد منها. بشكل افتراضي، يمكن اعتبار جميع هذه الأقفال كأقفال من المستوى 5 لأغراض المقارنة مع بقية التسلسل. يجب أن يتم الحصول على TSCtx فقط من مجموعة JIT من TSCtx، ويجب تحرير جميع الأقفال على ذلك TSCtx قبل إعادته إلى المجموعة. إذا كان يجب الحصول على عدة أقفال TSCtx في نفس الوقت (بسبب التجميع التكراري)، فيجب الحصول على الأقفال بالترتيب الذي تم فيه اقتراض TSCtxs من المجموعة.

التالي هو قفل من المستوى 5

  • جولياOJIT::EmissionMutex

الآتي هو قفل من المستوى 6، والذي يمكنه فقط التكرار للحصول على أقفال على مستويات أدنى:

  • كودجن
  • jlmodulesmutex

التالي هو قفل جذر شبه (نهاية المستوى -1)، مما يعني أنه يمكن الاحتفاظ فقط بقفل الجذر عند محاولة الحصول عليه:

  • typeinf

    هذا ربما يكون واحدًا من أكثر الأمور تعقيدًا، حيث يمكن استدعاء استنتاج النوع من العديد من النقاط

    حاليًا، تم دمج القفل مع قفل التوليد التلقائي، حيث يتصلان ببعضهما البعض بشكل متكرر.

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

  • آيولوك

  • قفلات ThreadSynchronizers الفردية

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

  • Libdl.LazyLibrary قفل

الجذر التالي هو قفل الجذر، مما يعني أنه لا ينبغي أن يتم الاحتفاظ بأي قفل آخر عند محاولة الحصول عليه:

  • توب ليفل

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

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

Broken Locks

الأقفال التالية معطلة:

  • توب ليفل

    لا يوجد الآن

    إصلاح: أنشئه

  • وحدة->قفل

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

    fix: استبدل بـ jl_modules_mutex؟

  • loading.jl: require و register_root_module

    هذا الملف يحتوي على مشاكل عديدة محتملة.

    إصلاح: يحتاج إلى أقفال

Shared Global Data Structures

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

تعديلات جدول الطريقة (def، cache): MethodTable->writelock

إعلانات النوع: قفل المستوى الأعلى

نوع التطبيق: قفل نوع الذاكرة

جداول المتغيرات العالمية : الوحدة->قفل

وحدة التسلسل: قفل المستوى الأعلى

JIT & استنتاج النوع : قفل توليد الشيفرة

تحديثات MethodInstance/CodeInstance : Method->writelock، قفل codegen

  • هذه محددة عند البناء وغير قابلة للتغيير:

    • specTypes
    • sparam_vals
    • def
    • مالك
  • تحدد هذه بواسطة jl_type_infer (أثناء الاحتفاظ بقفل التوليد):

    • ذاكرة التخزين المؤقت
    • rettype
    • مستنتج
    * valid ages
  • علامة inInference:

    • تحسين لتجنب الدخول مرة أخرى بسرعة في jl_type_infer بينما هو قيد التشغيل بالفعل
    • الحالة الفعلية (لإعداد inferred، ثم fptr) محمية بواسطة قفل توليد الشيفرة
  • مؤشرات الدالة:

    • تتحول هذه مرة واحدة، من NULL إلى قيمة، بينما يتم الاحتفاظ بقفل توليد الشيفرة
  • ذاكرة التخزين المؤقت لمولد الشيفرة (محتويات functionObjectsDecls):

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

LLVMContext : قفل التوليد

طريقة : Method->writelock

  • مصفوفة الجذور (المسلسل وتوليد الشيفرة)
  • استدعاء / تخصصات / تعديلات tfunc