Proper maintenance and care of multi-threading locks
Die folgenden Strategien werden verwendet, um sicherzustellen, dass der Code frei von Deadlocks ist (insbesondere durch die Behebung der 4. Coffman-Bedingung: zirkuläres Warten).
- Strukturieren Sie den Code so, dass jeweils nur ein Lock erworben werden muss.
- erwerben Sie immer gemeinsame Sperren in derselben Reihenfolge, wie in der folgenden Tabelle angegeben
- vermeiden Sie Konstrukte, die erwarten, dass unbeschränkte Rekursion erforderlich ist
Locks
Unten sind alle Schlösser, die im System existieren, sowie die Mechanismen zu ihrer Verwendung, die das Potenzial für Deadlocks vermeiden (kein Strauß-Algorithmus hier erlaubt):
Die folgenden sind definitiv Blatt-Sperren (Stufe 1) und dürfen keine anderen Sperren zu erwerben versuchen:
sichere Punkt
Beachten Sie, dass dieses Schloss implizit von
JL_LOCK
undJL_UNLOCK
erworben wird. Verwenden Sie die_NOGC
-Varianten, um dies für Level-1-Schlösser zu vermeiden.Während Sie dieses Schloss halten, darf der Code keine Zuweisungen vornehmen oder auf Safepoints treffen. Beachten Sie, dass es Safepoints gibt, wenn Zuweisungen vorgenommen werden, GC aktiviert / deaktiviert wird, Ausnahme-Frames betreten / wiederhergestellt werden und Schlösser genommen / freigegeben werden.
shared_map
finalizers
pagealloc
gcpermlock
flisp
jlinstackwalk (Win32)
ResourcePool<?>::mutex
RLST_mutex
llvmDruckenMutex
jlgesperrterstream::mutex
debuginfo_asyncsafe
inferenzzeitmutex
ExecutionEngine::SessionLock
flisp selbst ist bereits threadsicher, dieses Lock schützt nur den
jl_ast_context_list_t
Pool, ebenso schützen die ResourcePool<?>::mutexes nur den zugehörigen Ressourcenpool.
Der folgende ist ein Blattverschluss (Stufe 2) und erwirbt intern nur Sperren der Stufe 1 (Safepoint):
- globalrootslock
- Modul->sperren
- JLDebuginfoPlugin::PluginMutex
- neuabgeleitetmutex
Der folgende ist ein Level-3-Schloss, das nur intern Level-1- oder Level-2-Schlösser erwerben kann:
- Methode->writelock
- typecache
Der folgende ist ein Level-4-Schloss, das nur rekursiv Level-1-, Level-2- oder Level-3-Schlösser erwerben kann:
- MethodTable->writelock
Kein Julia-Code darf aufgerufen werden, während an dieser Stelle ein Lock gehalten wird.
orc::ThreadSafeContext (TSCtx) Locks nehmen einen besonderen Platz in der Lock-Hierarchie ein. Sie werden verwendet, um den globalen nicht-thread-sicheren Zustand von LLVM zu schützen, aber es kann eine beliebige Anzahl von ihnen geben. Standardmäßig können all diese Locks als Level-5-Locks für den Vergleich mit dem Rest der Hierarchie behandelt werden. Das Erwerben eines TSCtx sollte nur aus dem Pool der TSCtxs des JIT erfolgen, und alle Locks auf diesem TSCtx sollten freigegeben werden, bevor er an den Pool zurückgegeben wird. Wenn mehrere TSCtx-Locks gleichzeitig erworben werden müssen (aufgrund rekursiver Kompilierung), sollten die Locks in der Reihenfolge erworben werden, in der die TSCtxs aus dem Pool entliehen wurden.
Der folgende ist ein Level 5 Schloss
- JuliaOJIT::EmissionMutex
Die folgenden sind ein Level-6-Schloss, das nur rekursiv auf niedrigere Ebenen zugreifen kann, um Schlösser zu erwerben:
- codegen
- jlmodulemutex
Der folgende ist ein fast Root-Lock (Level End-1), was bedeutet, dass nur der Root-Lock gehalten werden darf, wenn versucht wird, ihn zu erwerben:
typeinf
dies ist vielleicht einer der kniffligsten, da die Typinferenz von vielen Punkten aus aufgerufen werden kann
Derzeit ist das Lock mit dem Codegen-Lock zusammengeführt, da sie sich gegenseitig rekursiv aufrufen.
Der folgende Lock synchronisiert IO-Operationen. Seien Sie sich bewusst, dass das Durchführen von I/O (zum Beispiel das Drucken von Warnmeldungen oder Debug-Informationen), während ein anderer oben aufgeführter Lock gehalten wird, zu tückischen und schwer zu findenden Deadlocks führen kann. SEIEN SIE SEHR VORSICHTIG!
iolock
Individuelle ThreadSynchronizers-Sperren
dies kann weiterhin gehalten werden, nachdem das iolock freigegeben wurde, oder ohne es erworben werden, aber seien Sie sehr vorsichtig, niemals zu versuchen, das iolock zu erwerben, während Sie es halten.
Libdl.LazyLibrary Sperre
Der folgende ist der Wurzelverschluss, was bedeutet, dass kein anderer Verschluss gehalten werden darf, wenn versucht wird, ihn zu erwerben:
oberste Ebene
dies sollte gehalten werden, während man versucht, eine Aktion auf oberster Ebene auszuführen (wie das Erstellen eines neuen Typs oder das Definieren einer neuen Methode): der Versuch, dieses Lock innerhalb einer gestuften Funktion zu erhalten, führt zu einer Deadlock-Bedingung!
Zusätzlich ist unklar, ob irgendein Code sicher parallel mit einem beliebigen Top-Level-Ausdruck ausgeführt werden kann, sodass es möglicherweise erforderlich ist, dass alle Threads zuerst einen Safepoint erreichen.
Broken Locks
Die folgenden Schlösser sind defekt:
oberste Ebene
existiert gerade nicht
fix: erstelle es
Modul->sperren
Dies ist anfällig für Deadlocks, da nicht sichergestellt werden kann, dass es in der richtigen Reihenfolge erworben wird. Einige Operationen (wie
import_module
) fehlen ein Lock.fix: ersetzen mit
jl_modules_mutex
?loading.jl:
require
undregister_root_module
Diese Datei hat möglicherweise zahlreiche Probleme.
fix: benötigt Schlösser
Shared Global Data Structures
Diese Datenstrukturen benötigen jeweils Sperren, da sie einen gemeinsamen veränderbaren globalen Zustand darstellen. Es ist die umgekehrte Liste der oben genannten Sperrprioritäten. Diese Liste enthält keine Ressourcen der Stufe 1, da sie einfach sind.
MethodTable-Modifikationen (def, cache) : MethodTable->writelock
Typdeklarationen : Top-Level-Sperre
Typanwendung: typecache-Sperre
Globale Variablen Tabellen : Modul->sperren
Modul-Serializer: Top-Level-Sperre
JIT & Typinferenz : Codegenerierungssperre
MethodInstance/CodeInstance-Updates: Methode->writelock, codegen-Sperre
- Diese sind bei der Konstruktion festgelegt und unveränderlich:
- specTypen
- sparam_vals
- def
- Besitzer
- Diese werden von
jl_type_infer
festgelegt (während der Codegenerierungssperre):
- Cache
- rettype
- abgeleitet
* valid ages
inInference
-Flag:
- Optimierung, um schnell zu vermeiden, dass
jl_type_infer
erneut aufgerufen wird, während es bereits läuft.- Der aktuelle Zustand (des Setzens von
inferred
, dannfptr
) ist durch den Codegenerierungssperre geschützt.
Funktionszeiger:
- diese Übergänge einmal, von
NULL
zu einem Wert, während das Codegen-Sperre gehalten wirdCode-Generator-Cache (der Inhalt von
functionObjectsDecls
):
- diese können mehrfach wechseln, aber nur solange der Codegen-Lock gehalten wird
- Es ist gültig, eine alte Version davon zu verwenden oder neue Versionen davon zu blockieren, sodass Rennen harmlos sind, solange der Code darauf achtet, keine anderen Daten in der Methodeninstanz (wie
rettype
) zu referenzieren und anzunehmen, dass sie koordiniert sind, es sei denn, der Codegen-Lock wird ebenfalls gehalten.
LLVMContext : Codegen-Sperre
Methode : Methode->writelock
- Wurzeln Array (Serializer und Codegen)
- aufrufen / spezialiserungen / tfunc änderungen