Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonでのスレッド同期の基本:Lockによる排他制御のやり方を解説

マルチスレッドプログラミングでは、複数のスレッドが同時に共有リソースへアクセスすると、データの不整合や競合状態(レースコンディション)が発生する恐れがあります。Pythonの標準ライブラリであるthreadingモジュールには、こうした問題を防ぐためのシンプルなロック機構が組み込まれており、これを利用することでスレッドの動作を同期させることができます。

Lockオブジェクトの生成

新しいロックは、Lock()メソッドを呼び出すだけで作成できます。このメソッドは新しいロックオブジェクトを返します。

threadLock = threading.Lock()

acquire()メソッドでロックを取得する

ロックオブジェクトのacquire(blocking)メソッドを使うと、スレッドを強制的に同期的に実行させられます。省略可能な引数blockingによって、ロックが取得できるまでスレッドを待機させるかどうかを制御できます。

  • blocking=0 の場合: ロックを取得できなければ即座に「0」を返し、取得できた場合は「1」を返します(ノンブロッキング動作)。
  • blocking=1 の場合: ロックが解放されるまで、スレッドはブロックされた状態で待ち続けます。

release()メソッドでロックを解放する

ロックが不要になったタイミングで、release()メソッドを呼び出してロックを解放します。これにより、待機していた次のスレッドがロックを取得できるようになります。

サンプルコード

以下は、2つのスレッドをロックで同期させる例です(Python 3系の記法で記述しています)。

#!/usr/bin/python3
import threading
import time

class myThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print("Starting " + self.name)
        # スレッドを同期するためロックを取得
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        # 次のスレッドのためにロックを解放
        threadLock.release()

def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
threads = []

# 新しいスレッドを作成
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# スレッドを開始
thread1.start()
thread2.start()

# スレッドをリストに追加
threads.append(thread1)
threads.append(thread2)

# すべてのスレッドの完了を待機
for t in threads:
    t.join()

print("Exiting Main Thread")

実行結果

上記のコードを実行すると、次のような出力が得られます。

Starting Thread-1
Starting Thread-2
Thread-1: Thu Mar 21 09:11:28 2013
Thread-1: Thu Mar 21 09:11:29 2013
Thread-1: Thu Mar 21 09:11:30 2013
Thread-2: Thu Mar 21 09:11:32 2013
Thread-2: Thu Mar 21 09:11:34 2013
Thread-2: Thu Mar 21 09:11:36 2013
Exiting Main Thread

処理の流れのポイント

  • 先にstartされたThread-1がロックを取得し、現在時刻の出力を3回繰り返します。
  • Thread-2はacquire()の時点でブロックされ、Thread-1がrelease()を実行するまで待機します。
  • そのため、Thread-1の出力がすべて終わってからThread-2の出力が始まり、スレッドが順番に実行されていることがわかります。
  • 最後にjoin()ですべてのスレッドの完了を待ち、メインスレッドが終了します。

このように、Lockオブジェクトによる取得・解放の仕組みを使えば、複数スレッドが共有リソースへ同時にアクセスすることを防ぎ、安全で予測可能な並行処理を実現できます。

  1. 【初心者向け】Pythonのissuperset()メソッドの使い方をわかりやすく解説

    はじめにこの記事では、Pythonのissuperset()メソッドについて、基本的な仕組みから実際のコード例まで詳しく解説します。issuperset()は、セット(集合)に対して使用できるメソッドで、引数として渡されたセットのすべての要素が、呼び出し元のセットに含まれているかどうかを判定します。呼び出し元のセットBが、引数のセットAのすべての要素を含んでいる場合 → True を返すセットAの要素がすべてBに含まれていない場合 → False を返すつまり、「BがAの上位集合(スーパーセット)であるかどうか」を判定するためのメソッドです。基本構文B.issuperset(A)この式は、Bが

  2. Pythonのグローバルインタプリタロック(GIL)とは?仕組みと課題をわかりやすく解説

    本記事では、Pythonのグローバルインタプリタロック(GIL:Global Interpreter Lock)について詳しく解説します。GILとは、複数のスレッドが同時にPythonインタプリタのコードを実行できないよう制御する排他制御機構(ロック)のことです。この仕組みにより、マルチスレッド環境であっても、実際には一度に1つのスレッドしかPythonバイトコードを実行できません。そのため、GILはPython 3.x以前における設計上の課題として認識されることが多く、真の意味でのマルチスレッド処理を妨げる要因となっています。GILが導入された理由Pythonは自動ガベージコレクション(GC