Proper maintenance and care of multi-threading locks
次の戦略は、コードがデッドロックフリーであることを保証するために使用されます(一般的に4番目のコフマン条件:循環待機に対処することによって)。
- コードを構造化して、同時に取得する必要があるロックを1つだけにする。
- 常に、以下の表に示された順序で共有ロックを取得してください。
- 無制限の再帰が必要だと期待される構造を避ける
Locks
以下は、システムに存在するすべてのロックと、それらを使用するためのメカニズムであり、デッドロックの可能性を回避します(ここではオーストリッチアルゴリズムは許可されていません):
以下は確実にリーフロック(レベル1)であり、他のロックを取得しようとするべきではありません:
safepoint
このロックは、
JL_LOCKとJL_UNLOCKによって暗黙的に取得されることに注意してください。レベル 1 ロックの場合は、_NOGCバリアントを使用してそれを回避してください。このロックを保持している間、コードはどのような割り当ても行わず、セーフポイントに到達してはいけません。割り当てを行う際、GCの有効化/無効化、例外フレームの入出力、ロックの取得/解放を行う際にはセーフポイントが存在することに注意してください。
shared_map
ファイナライザー
pagealloc
gcpermlock
flisp
jlinstackwalk (Win32)
ResourcePool<?>::mutex
RLST_mutex
llvm印刷ミューテックス
jlロックされたストリーム::ミューテックス
debuginfo_asyncsafe
推論タイミングミューテックス
ExecutionEngine::SessionLock
flisp自体はすでにスレッドセーフであり、このロックは
jl_ast_context_list_tプールを保護するだけです。同様に、ResourcePool<?>::mutexesは関連するリソースプールを保護するだけです。
以下はリーフロック(レベル2)であり、内部でレベル1ロック(セーフポイント)をのみ取得します:
- globalrootslock
- Module->ロック
- JLDebuginfoPlugin::PluginMutex
- newlyinferredmutex
以下はレベル3のロックであり、内部的にはレベル1またはレベル2のロックのみを取得できます:
- メソッド->writelock
- タイプキャッシュ
以下はレベル4のロックであり、レベル1、2、または3のロックを取得するためにのみ再帰することができます:
- MethodTable->writelock
このポイントより上でロックを保持している間は、Juliaコードを呼び出すことはできません。
orc::ThreadSafeContext (TSCtx) ロックは、ロック階層の特別な位置を占めています。これらは LLVM のグローバルなスレッドセーフでない状態を保護するために使用されますが、任意の数のロックが存在する可能性があります。デフォルトでは、これらのロックは階層の他の部分と比較する目的でレベル 5 のロックとして扱われることがあります。TSCtx を取得するのは JIT の TSCtx プールからのみ行うべきであり、その TSCtx に対するすべてのロックはプールに戻す前に解放されるべきです。複数の TSCtx ロックを同時に取得する必要がある場合(再帰的コンパイルのため)、ロックはプールから借りた TSCtx の順序で取得するべきです。
以下はレベル5のロックです。
- JuliaOJIT::EmissionMutex
以下はレベル6のロックであり、下位レベルのロックを取得するためにのみ再帰することができます:
- jlmodulesmutex
次のロックはIO操作を同期します。他のロックを保持している間にI/O(警告メッセージやデバッグ情報の印刷など)を行うと、厄介で見つけにくいデッドロックが発生する可能性があるため、十分に注意してください!
iolock
個別のThreadSynchronizersロック
これは、iolockを解除した後も保持される可能性がありますが、保持している間にiolockを取得しようとしないように非常に注意してください。
Libdl.LazyLibrary ロック
以下はレベル7のロックで、他のロックを保持していないときにのみ取得できます:
- worldcounterlock
以下はルートロックであり、これを取得しようとする際には他のロックを保持してはならないことを意味します:
toplevel
これは、新しいタイプを作成したり、新しいメソッドを定義したりするなどのトップレベルのアクションを試みる際に保持されるべきです。ステージされた関数内でこのロックを取得しようとすると、デッドロック状態を引き起こします!
さらに、任意のトップレベルの式と安全に並行して実行できるコードがあるかどうかは不明であるため、すべてのスレッドが最初にセーフポイントに到達する必要があるかもしれません。
Broken Locks
以下のロックは壊れています:
toplevel
現在は存在しません。
修正: 作成する
Module->ロック
これはデッドロックに対して脆弱であり、順番に取得されているかどうかを確信できません。一部の操作(
import_moduleなど)はロックが欠けています。修正:
jl_modules_mutexに置き換えますか?loading.jl:
requireとregister_root_moduleこのファイルには潜在的に多数の問題があります。
修正: ロックが必要です
Shared Global Data Structures
これらのデータ構造は、共有される可変グローバル状態のため、各々ロックが必要です。これは上記のロック優先リストの逆リストです。このリストには、その単純さからレベル1のリーフリソースは含まれていません。
MethodTable の変更 (def, cache) : MethodTable->writelock
型宣言 : トップレベルロック
タイプアプリケーション : typecache lock
グローバル変数テーブル : モジュール->ロック
モジュールシリアライザー : トップレベルロック
JIT と型推論 : コード生成ロック
MethodInstance/CodeInstance の更新 : Method->writelock
これらは建設時に設定され、不変です:
- specTypes
- sparam_vals
- def
- オーナー
関数ポインタ:
- これらの遷移は一度だけ、
NULLから値に移行し、これはJIT内部で調整されています。
メソッド : Method->writelock
- roots 配列 (シリアライザーとコード生成)
- 呼び出し / 専門化 / tfunc 修正