Proper maintenance and care of multi-threading locks

次の戦略は、コードがデッドロックフリーであることを保証するために使用されます(一般的に4番目のコフマン条件:循環待機に対処することによって)。

  1. コードを構造化して、同時に取得する必要があるロックを1つだけにする。
  2. 常に、以下の表に示された順序で共有ロックを取得してください。
  3. 無制限の再帰が必要だと期待される構造を避ける

Locks

以下は、システムに存在するすべてのロックと、それらを使用するためのメカニズムであり、デッドロックの可能性を回避します(ここではオーストリッチアルゴリズムは許可されていません):

以下は確実にリーフロック(レベル1)であり、他のロックを取得しようとするべきではありません:

  • safepoint

    このロックは、JL_LOCKJL_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: requireregister_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 修正