concurrent.futuresでLockの使い方
Lockとは?
- Lock は、複数のスレッドやプロセスが同時に共有変数やリソースにアクセスする際に、そのアクセスを制御するための仕組みです。
- Lockを使うことで、データの整合性 を守ることができます。これにより、複数のスレッドが同時に変数を変更してしまう「競合状態(race condition)」を防ぐことができます。
例題 1: import threading
を使った Lock の利用
まず、import threading
を使って Lock を導入する例を見てみましょう。
コード: import threading を使う例
from threading import Thread, Lock
import time
database_value = 0
def increase(lock):
global database_value
with lock :
local_copy = database_value
# processing
local_copy += 1
time.sleep(0.1)
database_value = local_copy
# スクリプトを直接実行する場合のみメイン関数を呼び出す
if __name__ == "__main__":
lock = Lock()
print("start value", database_value)
thread_1 = Thread(target=increase, args=(lock,))
thread_2 = Thread(target=increase, args=(lock,))
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
print("end value", database_value)
解説
-
Lockの目的: 上記のコードでは、複数のスレッドが
counter
という共有変数を同時に更新します。Lockを使うことで、1つのスレッドが変数の変更を完了するまで、他のスレッドのアクセスを一時的に停止します。これにより、データの不整合が防止されます。 -
使い方:
with lock
という構文で、Lockを使ったブロックを定義し、そのブロック内での処理が終わるまで他のスレッドが変数にアクセスできないようにしています。
例題 2: concurrent.futures
を使った Lock の利用
次に、from concurrent.futures
を使って、同じように Lock を導入する例を見てみましょう。
コード: concurrent.futures を使う例
from concurrent.futures import ThreadPoolExecutor
import threading
import time
database_value = 0
lock = threading.Lock()
def increase():
global database_value
with lock:
local_copy = database_value
# processing
local_copy += 1
time.sleep(0.1)
database_value = local_copy
if __name__ == "__main__":
print("start value", database_value)
# ThreadPoolExecutorを使用して2つのスレッドを実行
with ThreadPoolExecutor(max_workers=2) as executor:
# 2つのタスクを同時に実行
futures = [
executor.submit(increase)
for _ in range(2)
]
# すべての結果を待つ
for future in futures:
future.result()
print("end value", database_value)
解説
- このコードでは、
ThreadPoolExecutor
を使って、最大4つのスレッドでincrement()
関数を並行に実行します。 - Lockの使い方は、
import threading
を使った場合と同様です。with lock
を使うことで、スレッドごとの変数更新が保護されます。 -
concurrent.futures
を使う理由: これを使うと、スレッドの管理がよりシンプルになり、submit()
メソッドで関数を並行に実行できます。これにより、スレッドの作成と管理が統一された構造で行えます。
Lockが必要な理由
- データ競合の防止: Lockを使うことで、複数のスレッドが同時に同じ変数にアクセスし、データを不整合にすることを防げます。これにより、プログラムの動作が正確になります。
- 例外的な状況の回避: Lockを使わない場合、変数の更新中に他のスレッドがアクセスする可能性があり、最終的な計算結果が正しくない場合があります。
まとめ
- Lockは、並行処理においてデータの整合性を守るために必要です。
-
concurrent.futures
でもLockを使うことができ、よりシンプルに並行処理を実装できます。 - Lockを適切に使うことで、データの競合を防ぎ、プログラムが意図した通りに動作するようにできます。
参考資料
Threading in Python - Advanced Python 16 - Programming Tutorial
https://youtu.be/usyg5vbni34?si=3skpeFP_TZ_L1Yp1