Mímisbrunnr知恵の泉

← コンピュータ基礎 一覧

🎓 レベル:発展 | 重要度:A(必須)

📎 前提:排他制御(ロック・セマフォ・ミューテックス) | 関連:競合状態とクリティカルセクション並行性のパターン

要点(BLUF)

概念 ── 待ち合いで固まる

排他制御(ロック・セマフォ・ミューテックス) のロックは諸刃の剣です。2つのロックを2スレッドが逆順に取りに行くと、こうなります。

flowchart LR
    T1["スレッドT1(ロックA保持中・Bを待つ)"] -->|"Bが欲しい"| B["ロックB"]
    B -->|"T2が保持"| T2["スレッドT2(ロックB保持中・Aを待つ)"]
    T2 -->|"Aが欲しい"| A["ロックA"]
    A -->|"T1が保持"| T1

T1はB待ち、T2はA待ち。互いに相手が手放すのを待つが、どちらも自分の持つものを手放さない。循環した待ちができ、永久に止まります。競合状態(壊れた値)と違い、デッドロックは何も進まないのが症状です。

仕組み ── デッドロックの4条件(Coffman条件)

デッドロックが起きるには、次の4つがすべて同時に成立する必要があります(必要条件)。

条件意味
相互排他資源は同時に1つの実行しか持てない
保持と待機資源を持ったまま、別の資源を待つ
横取り不可持っている資源を強制的に奪えない(自発解放のみ)
循環待ち待ちの関係が輪を作る(A待ちB、B待ちC、…、待ちA)

重要なのは「すべて必要」=どれか1つでも崩せばデッドロックは起きないこと。これが対策の指針になります。

仕組み ── 4つの戦略

1. 予防(prevention)── 4条件のどれかを構造的に潰す

2. 回避(avoidance)── 危険な割り当てを事前に拒む

資源要求のたびに「この割り当てを認めると、将来デッドロックしうる(安全でない)状態になるか」を判定し、危険なら待たせる。銀行家のアルゴリズムが代表。ただし最大要求量を事前に知る必要があり、汎用OSでは重すぎてあまり使われません。

3. 検出と回復(detection & recovery)

デッドロックを許し、資源割り当てグラフに循環ができたら検出して回復する(プロセスを強制終了する、資源を横取りしてロールバックする)。データベースのトランザクションが採る方式(デッドロック検出→片方を中断)。

4. 無視(駝鳥アルゴリズム)

めったに起きないなら何もしないで、起きたら再起動。多くの汎用OSの実用的な割り切り。

具体例 ── 逆順ロックを再現し、順序統一で消す

固まると観察できないので、ラボでは acquire(timeout=...) で「取れない=デッドロック」を検出します([[#対応ラボ]] の 04-03_deadlock.py)。

def worker(name, first, second):
    first.acquire(); time.sleep(0.1)
    got = second.acquire(timeout=1.0)   # 取れなければデッドロックとみなす
    ...
# T1: A->B、T2: B->A(逆順=循環待ち)

実行結果(実機):

== 逆順ロック(デッドロックが起きる)==
T2: 2本目が取れない -> デッドロック検出(相手が手放さない)
T1: 2本目が取れない -> デッドロック検出(相手が手放さない)

== ロック順序を統一(A->Bに揃える)-> 回避 ==
T1: A->B の順で取得 -> 処理OK
T2: A->B の順で取得 -> 処理OK
循環待ちが消えるのでデッドロックしない

逆順だと両者が2本目を取れず固まる。取得順序をA→Bに統一するだけで循環待ちが消え、すんなり通ります。これが実務で最も使われる予防策です。

仕組みの直観 ── なぜこの設計か

⚠️ よくある誤解・落とし穴

対応ラボ

cs-foundations-study/labs/04-03_deadlock.py(実行して逆順での停止と、順序統一での回避を確認済み)。

関連

第4章 並行処理と同期 目次