Select Language

AI Technology Community

8.6、Pythonスレッドデッドロックの原因と解決方法

デッドロックはマルチスレッドプログラミングでよく議論される問題です。いわゆるデッドロックとは、スレッドがあるリソースを無期限に待ち続けることです。

最も単純なデッドロック現象は、スレッドが自分自身がすでに取得しているロックを待つことです。そのロックはすでに自分自身が取得しているため、2回目にそのロックを申請すると待ち行列に入れられますが、この待ち時間は永遠になります。以下のコードはこの状況を示しています。

フォーマットコピー
import sys, time
if sys.version_info.major == 2:
    import thread
else:
    import _thread as thread
lock = thread.allocate_lock()           # ロックを作成する
def thread_entry():                     # スレッドのエントリー関数
    global lock
    print("ロック取得前 - 1")
    lock.acquire()
    print("ロック取得後 - 1")
    print("ロック取得前 - 2")
    lock.acquire()                      # ここでデッドロックが発生し、以降のコードは実行されない
    print("ロック取得後 - 1")
    lock.release()
    lock.release()
def start_threads():                    # 子スレッドを起動する
    t1 = thread.start_new_thread(thread_entry, tuple())
    time.sleep(5)
    print("メインスレッド終了")           # メインスレッドが終了する
if __name__=='__main__':
    start_threads()

実行結果は以下の通りです:

$ python deadlockDemo1.py
ロック取得前 - 1       # 9行目の出力
ロック取得後 - 1          # 11行目の出力
ロック取得前 - 2       # 12行目の出力
メインスレッド終了                # 20行目の出力、子プロセスは13行目で停止する


前述の内容は自分自身をロックしてしまうケースで、このような状況は比較的少ないです。より多いのは以下のようなケースです。スレッドAがあるリソースR1を取得し、同時にリソースR2を申請します。スレッドBがリソースR2を取得し、同時にリソースR1を申請します。このときデッドロックが発生し、スレッドAはリソースR2を取得できないため待機状態になり、スレッドBもリソースR1を取得できないため待機状態になります。以下のコードはこの状況を示しています。

import sys, time                                # timeライブラリをインポートする
if sys.version_info.major == 2: # Python 2
    import thread
else:                                           # Python 3
        import _thread as thread
        lock1 = thread.allocate_lock()  # リソースR1
        lock2 = thread.allocate_lock()  # リソースR2
def thread_entry_A():                   # スレッドAのエントリー関数
    global lock1, lock2
    print("スレッドA: ロック1取得前")
    lock1.acquire()                              # リソースR1を取得する
    print("スレッドA: ロック1取得後")
    time.sleep(3)
    print("スレッドA: ロック2取得前")
    lock2.acquire()                               # リソースR2を申請する、ここでデッドロックが発生する
    print("スレッドA: ロック2取得後")
    lock1.release()                               # リソースR1を解放する
    lock2.release()                               # リソースR2を解放する
def thread_entry_B():                           # スレッドBのエントリー関数
    global lock1, lock2
    print("スレッドB: ロック2取得前")
    lock2.acquire()                             # リソースR2を取得する
    print("スレッドB: ロック2取得後")
    time.sleep(3)
    print("スレッドB: ロック1取得前")
    lock1.acquire()                             # リソースR1を申請する、ここでデッドロックが発生する
    print("スレッドB: ロック1取得後")
    lock1.release()                             # リソースR1を解放する
    lock2.release()                             # リソースR2を解放する
def start_threads():
    t1 = thread.start_new_thread(thread_entry_A, tuple())
    t1 = thread.start_new_thread(thread_entry_B, tuple())
    time.sleep(5)
    print("メインスレッド終了")           # メインスレッドが終了する、プロセスも終了する
if __name__=='__main__':                # スクリプトを実行している場合で、モジュールをインポートしていない場合
    start_threads()

実行結果は以下の通りです:

$ python deadlockDemo2.py               # スクリプトを実行する
スレッドA: ロック1取得前          # 10行目の出力
スレッドA: ロック1取得後           # 12行目の出力
スレッドB: ロック2取得前          # 21行目の出力
スレッドB: ロック2取得後           # 23行目の出力
スレッドA: ロック2取得前          # 14行目の出力、デッドロック状態に入る
スレッドB: ロック1取得前          # 25行目の出力、デッドロック状態に入る
メインスレッド終了                        # メインスレッドが終了する


デッドロックはスレッド間で発生するもので、一般的にはあるスレッドAが別のスレッドBのあるリソースを取得したいために発生します。したがって、この関係は図1で表すことができます。



図1 スレッドAがスレッドBが持つリソースを待っている


しかし、これだけではデッドロックには至りません。なぜなら、スレッドBは自分が持つリソースを解放するため、そのときスレッドAは再び実行を続けることができます。デッドロックの条件の1つは、デッドロックに関与するすべてのスレッドが実行を続けられなくなることです。前述の例では、スレッドAは実行を続けることができませんが、スレッドBは実行を続けることができます。したがって、このときはデッドロックは発生していません。もしスレッドBもスレッドAが持つあるリソースを待っている場合はどうなるでしょうか? そうすると、スレッドBも実行を続けることができなくなります。これは前述の例のコードで示されている状況で、図2で表すことができます。


図2 スレッドAとスレッドBのデッドロック


このとき、図上にループが形成されていることがわ

post
  • 10

    item of content
プロセスはリソースを分配する単位であり、スレッドはオペレーティングシステムがスケジューリングできる最小の単位です。
通常、プロセスには少なくとも1つのスレッドが含まれ、複数のスレッドがある場合はその中にメインスレッドが含まれます。同じプロセス内のすべてのスレッドはシステムリソースを共有しますが、それぞれが独立したスタック、レジスタ環境、およびローカルストレージを持っています。
マルチスレッドの利点は、複数のタスクを同時に実行できることです。システムに複数の計算ユニットがある場合、複数のスレッドはそれぞれの計算ユニットで並行して動作することができ、これによりシステムの処理効率が大幅に向上します。
多くの場合、プロセスはスレッドよりも大きい単位であり、通常1つのプロセスは複数のスレッドを含むことができます。プロセスの隔離効果はスレッドよりも優れているため、マルチプロセスを使用するとマルチスレッドよりも安全です。ただし、マルチプロセスの欠点はマルチスレッドよりもスケジューリングが重く、効率が低いことです。