はじめに
Python のスレッド機能により、開発者は並列処理の力を活用することができますが、スレッド間で共有リソースを管理するのは困難な作業になることがあります。このチュートリアルでは、Python のスレッドにおける共有データの同期プロセスを案内し、スレッドセーフな実行とデータの整合性を確保する方法を説明します。
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
Python のスレッド機能により、開発者は並列処理の力を活用することができますが、スレッド間で共有リソースを管理するのは困難な作業になることがあります。このチュートリアルでは、Python のスレッドにおける共有データの同期プロセスを案内し、スレッドセーフな実行とデータの整合性を確保する方法を説明します。
Python の組み込み threading モジュールを使用すると、スレッドを作成および管理できます。スレッドは、単一のプロセス内で同時に実行できる軽量な実行単位です。スレッドは、I/O 操作の処理、バックグラウンドでのデータ処理、または複数のクライアント要求への応答など、複数のタスクを同時に実行する必要がある場合に便利です。
スレッドは、単一のプロセス内で独立した実行シーケンスです。同じメモリ空間を共有するため、同じ変数やデータ構造にアクセスして変更することができます。このようなリソースへの共有アクセスは、同期の問題を引き起こす可能性があります。これについては次のセクションで説明します。
Python でスレッドを使用することには、いくつかの利点があります。
スレッドは強力ですが、いくつかのチャレンジも伴います。
次のセクションでは、Python スレッドにおける共有リソースの同期について詳しく説明します。
複数のスレッドが変数やデータ構造などの同じ共有リソースにアクセスすると、競合状態 (race condition) やその他の同期問題が発生する可能性があります。これらの問題により、データの破損、予期しない動作、またはアプリケーションのクラッシュさえも引き起こされることがあります。これらの問題を解決するために、Python は共有データを同期するためのいくつかのメカニズムを提供しています。
競合状態は、計算の最終結果が、共有データに対する複数のスレッドの操作の相対的なタイミングまたはインターリーブに依存する場合に発生します。これにより、予測不能で不正確な結果が生じることがあります。
次の例を考えてみましょう。
import threading
counter = 0
def increment_counter():
global counter
for _ in range(1000000):
counter += 1
threads = []
for _ in range(2):
t = threading.Thread(target=increment_counter)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter value: {counter}")
この例では、2 つのスレッドがそれぞれ共有の counter
変数を 1,000,000 回インクリメントしています。しかし、競合状態のため、counter
の最終値は予想通りの 2,000,000 にならないことがあります。
Python の threading
モジュールは、共有リソースへのアクセスを管理するのに役立ついくつかの同期プリミティブを提供しています。
これらの同期プリミティブを使用することで、スレッドが共有リソースに安全かつ協調的にアクセスするようにすることができ、競合状態やその他の同期問題を防ぐことができます。
次のセクションでは、これらの同期技術を Python アプリケーションで使用する実践的な例を探っていきます。
ここまでで Python のスレッドにおける共有データの同期の基本概念を説明しました。では、様々な同期プリミティブを使用する実践的な例を見ていきましょう。
ロックは Python で最も基本的な同期プリミティブです。一度に 1 つのスレッドだけがコードの重要な部分にアクセスできるようにします。共有カウンターを保護するためにロックを使用する例を次に示します。
import threading
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
for _ in range(1000000):
with lock:
counter += 1
threads = []
for _ in range(2):
t = threading.Thread(target=increment_counter)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter value: {counter}")
この例では、lock
オブジェクトを使用して、counter
変数がインクリメントされるコードの重要な部分に一度に 1 つのスレッドだけがアクセスできるようにしています。
セマフォは、限られた数のリソースへのアクセスを制御するために使用されます。同時に接続できるデータベース接続数を制限するためにセマフォを使用する例を次に示します。
import threading
import time
database_connections = 3
connection_semaphore = threading.Semaphore(database_connections)
def use_database():
with connection_semaphore:
print(f"{threading.current_thread().name} acquired a database connection.")
time.sleep(2) ## Simulating database operation
print(f"{threading.current_thread().name} released a database connection.")
threads = []
for _ in range(5):
t = threading.Thread(target=use_database)
threads.append(t)
t.start()
for t in threads:
t.join()
この例では、connection_semaphore
を使用して、同時に接続できるデータベース接続数を 3 つに制限しています。各スレッドは、データベース接続を使用する前にセマフォから「許可」を取得する必要があります。
条件変数を使用すると、スレッドは特定の条件が満たされるまで実行を待機することができます。キュー内のアイテムの生産と消費を調整するために条件変数を使用する例を次に示します。
import threading
import time
queue = []
queue_size = 5
queue_condition = threading.Condition()
def producer():
with queue_condition:
while len(queue) == queue_size:
queue_condition.wait()
queue.append(1)
print(f"{threading.current_thread().name} produced an item. Queue size: {len(queue)}")
queue_condition.notify_all()
def consumer():
with queue_condition:
while not queue:
queue_condition.wait()
item = queue.pop(0)
print(f"{threading.current_thread().name} consumed an item. Queue size: {len(queue)}")
queue_condition.notify_all()
producer_threads = [threading.Thread(target=producer) for _ in range(2)]
consumer_threads = [threading.Thread(target=consumer) for _ in range(3)]
for t in producer_threads + consumer_threads:
t.start()
for t in producer_threads + consumer_threads:
t.join()
この例では、queue_condition
変数を使用して、キュー内のアイテムの生産と消費を調整しています。生産者はキューに空きがあるのを待ち、消費者はキューにアイテムがあるのを待ちます。
これらの例は、Python の threading
モジュールが提供する様々な同期プリミティブを使用して、共有リソースを効果的に管理し、一般的な並行性の問題を回避する方法を示しています。
この包括的な Python チュートリアルでは、マルチスレッドアプリケーションにおいて共有リソースを効果的に同期する方法を学びます。ロック、セマフォ、条件変数などの Python の組み込み同期プリミティブを理解することで、並行アクセスを調整し、競合状態 (race condition) を回避し、Python プログラムの安定性と信頼性を確保することができます。