この記事では、Pythonのthreadingモジュールを使用した排他制御について紹介します。
目次
実装例
以下の例は、Pythonのthreadingモジュールを使用して2つのスレッドを並行して実行し、共通のグローバル変数counterをインクリメントするコードです。
import threading
import time
import random
# グローバル変数
counter = 0
# ロックオブジェクトを作成
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock:
# クリティカルセクション
temp = counter
# スリープを追加してレースコンディションを誘発
time.sleep(random.random() / 1000000)
counter = temp + 1
# スレッドを作成
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)
# スレッドを開始
thread1.start()
thread2.start()
# スレッドの終了を待機
thread1.join()
thread2.join()
print(f'Final counter value: {counter}')
ライブラリのインポート
import threading
import time
import random
- スレッドを作成・管理するためにthreadingモジュールをインポートします。
- 時間管理のためにtimeモジュール、ランダムなスリープ時間を生成するためにrandomモジュールをインポートします。
ロックオブジェクトの作成
lock = threading.Lock()
スレッド間の排他制御を行うためのロックオブジェクトを作成します。
インクリメント関数の定義
def increment():
global counter
for _ in range(100000):
# with lock: # ロックをコメントアウト
# クリティカルセクション
temp = counter
# スリープを追加してレースコンディションを誘発
time.sleep(random.random() / 10000)
counter = temp + 1
counterをインクリメントする関数を定義します。
一時変数tempにcounterの値を保存し、ランダムな短時間スリープを行ってスレッドの競合を意図的に誘発しています。
スレッドの作成と開始
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)
thread1.start()
thread2.start()
2つのスレッドを作成し、それぞれincrement関数をターゲットに設定してスレッドを開始します。
スレッドの終了を待機
thread1.join()
thread2.join()
メインスレッドが2つのスレッドの終了を待機します。
実行結果
このプログラムを実行すると以下のメッセージが標準出力に吐かれます。スピンロックでカウンタを排他しているため、最終的にカウンタの値が200000になっています。
Final counter value: 200000
排他を行わずに実行すると以下のようになります。排他制御をしていないために競合が発生し、正確にカウンタをインクリメントできていないことが分かります。
Final counter value: 100002