Python スレッド化でロック、条件、セマフォアを使いこなす with ステートメント
ロック
ロックは、リソースへの排他アクセスを許可する同期オブジェクトです。一度に1つのスレッドだけがロックを取得でき、ロックが解放されるまで他のスレッドはそのリソースにアクセスできません。これは、銀行口座の残高のような共有変数を更新する場合などに役立ちます。
Python では、threading.Lock
オブジェクトを使用してロックを作成できます。ロックを取得するには、acquire()
メソッドを呼び出し、解放するには release()
メソッドを呼び出します。
from threading import Lock
lock = Lock()
def withdraw(amount):
with lock:
# 預金口座から `amount` を引き出す
pass
def deposit(amount):
with lock:
# 預金口座に `amount` を預ける
pass
上記コードでは、lock
オブジェクトを使用して、預金口座へのアクセスを同期しています。withdraw()
関数と deposit()
関数はどちらも with
ステートメントを使用しており、これはロックを自動的に取得して解放します。
条件
条件は、特定の条件が満たされるのを待っているスレッドをブロックする同期オブジェクトです。条件変数の wait()
メソッドを呼び出すと、そのスレッドはブロックされ、条件がシグナルされるまで待機します。notify()
または notify_all()
メソッドを呼び出すことで、条件をシグナルできます。
条件は、複数のスレッドが同じリソースを待っている状況で役立ちます。たとえば、生産者と消費者の問題では、生産者スレッドは製品が完成するまで待機する必要があり、消費者スレッドは製品が利用可能になるまで待機する必要があります。
Python では、threading.Condition
オブジェクトを使用して条件を作成できます。
from threading import Condition
condition = Condition()
def producer():
while True:
# 製品を生産する
condition.acquire()
# 製品が利用可能になったことをシグナルする
condition.notify()
condition.release()
def consumer():
while True:
condition.acquire()
# 製品を消費する
condition.wait()
condition.release()
上記コードでは、condition
オブジェクトを使用して、生産者スレッドと消費者スレッドを同期しています。producer()
関数は製品を生産し、condition.notify()
を呼び出して製品が利用可能になったことをシグナルします。consumer()
関数は condition.wait()
を呼び出して製品が利用可能になるのを待機し、製品を消費してから condition.release()
を呼び出してロックを解放します。
セマフォア
セマフォアは、リソースの利用可能数をカウントする同期オブジェクトです。スレッドがリソースにアクセスするには、まずセマフォアからトークンを取得する必要があります。トークンが利用可能でない場合は、スレッドはブロックされます。リソースの使用が完了したら、スレッドはトークンを解放する必要があります。
セマフォアは、複数のスレッドが限られた数のリソースを共有する必要がある場合に役立ちます。たとえば、プリンターへのアクセスを制御する場合などに使用できます。
Python では、threading.Semaphore
オブジェクトを使用してセマフォアを作成できます。
from threading import Semaphore
semaphore = Semaphore(3)
def print_document():
semaphore.acquire()
# ドキュメントを印刷する
semaphore.release()
上記コードでは、semaphore
オブジェクトを使用して、3つのプリンターへのアクセスを制御しています。print_document()
関数は semaphore.acquire()
を呼び出してトークンを取得し、プリンターを使用します。プリンターの使用が完了したら、semaphore.release()
を呼び出してトークンを解放します。
ロック、条件、セマフォアは、Python でスレッドを同期するために使用できる強力なツールです。これらのオブジェクトを適切に使用することで、データ競合を回避し、安全で効率的な並行プログラムを作成することができます。
- [Python で条件変数
from threading import Lock
lock = Lock()
def withdraw(amount):
with lock:
# 預金口座から `amount` を引き出す
pass
def deposit(amount):
with lock:
# 預金口座に `amount` を預ける
pass
from threading import Condition
condition = Condition()
def producer():
while True:
# 製品を生産する
condition.acquire()
# 製品が利用可能になったことをシグナルする
condition.notify()
condition.release()
def consumer():
while True:
condition.acquire()
# 製品を消費する
condition.wait()
condition.release()
from threading import Semaphore
semaphore = Semaphore(3)
def print_document():
semaphore.acquire()
# ドキュメントを印刷する
semaphore.release()
イベント
イベントは、スレッド間でシグナルを送信するための簡単な方法です。イベントオブジェクトを作成し、set()
メソッドを呼び出してシグナルを送信し、wait()
メソッドを呼び出してシグナルを待機できます。
from threading import Event
event = Event()
def producer():
# 製品を生産する
event.set()
def consumer():
event.wait()
# 製品を消費する
producer()
consumer()
このコードでは、event
オブジェクトを使用して、生産者スレッドと消費者スレッドを同期しています。producer()
関数は製品を生産し、event.set()
を呼び出して製品が利用可能になったことをシグナルします。consumer()
関数は event.wait()
を呼び出して製品が利用可能になるのを待機してから、製品を消費します。
キュー
キューは、スレッド間でデータをやり取りするための方法です。キューオブジェクトを作成し、put()
メソッドを使用してデータをキューに追加し、get()
メソッドを使用してデータをキューから取得できます。
from threading import Queue
queue = Queue()
def producer():
# 製品を生産する
queue.put(product)
def consumer():
product = queue.get()
# 製品を消費する
producer()
consumer()
このコードでは、queue
オブジェクトを使用して、生産者スレッドと消費者スレッド間で製品をやり取りしています。producer()
関数は製品を生産し、queue.put()
を呼び出して製品をキューに追加します。consumer()
関数は queue.get()
を呼び出してキューから製品を取得し、製品を消費します。
複数のスレッドが同じオブジェクトにアクセスできる場合、そのオブジェクトを同期する必要があります。これは、ロック、条件変数、またはアトミック操作を使用して行うことができます。
from threading import Lock
class Counter:
def __init__(self):
self.count = 0
self.lock = Lock()
def increment(self):
with self.lock:
self.count += 1
counter = Counter()
def worker():
for _ in range(100):
counter.increment()
threads = []
for _ in range(4):
thread = threading.Thread(target=worker)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(counter.count)
このコードでは、Counter
クラスを使用して、複数のスレッドから安全にアクセスできるカウンタを作成しています。increment()
メソッドは lock
オブジェクトを使用して同期されているため、複数のスレッドが同時にカウンタをインクリメントしても、データ競合が発生しません。