Python - スレッドの同期

こんにちは、未来のPythonの魔法使いたち!今日は、スレッド同期のワクワクする世界に旅立ちます。あなたがオーケストラを指揮していて、それぞれの音楽家がスレッドであり、彼らがすべて和音で演奏するように調整する必要があると想象してみてください。それは、プログラミングにおけるスレッド同期の基本となります!

Python - Synchronizing Threads

ロックを使用したスレッドの同期

まず、私たちの同期ツールキットのもっとも基本的なツールを見てみましょう:ロックです。ロックをホテルの部屋のドアに挂けた「邪魔しない」のサインと考えてください。スレッドがロックを獲得すると、そのサインを掲げて他のスレッドに「おっと、今忙しいんだ!」と伝えるのに似ています。

以下の簡単な例でこの概念を説明します:

import threading
import time

# 共有リソース
counter = 0

# ロックを作成
lock = threading.Lock()

def increment():
global counter
for _ in range(100000):
lock.acquire()
counter += 1
lock.release()

# 2つのスレッドを作成
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

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

# スレッドが終了するのを待つ
thread1.join()
thread2.join()

print(f"最終カウンター値: {counter}")

この例では、2つのスレッドがインクリメントを試みる共有リソースcounterがあります。ロックを使用しない場合、競合状態が発生し、両方のスレッドが同時にカウンターをインクリメントしようとする可能性があり、結果として誤った結果になるかもしれません。

lock.acquire()をカウンターを変更する前に、そしてlock.release()を変更後に使用することで、一度に1つのスレッドだけがカウンターをインクリメントできるようにします。それは、リレーのランナーがボウンを渡すのに似ています - ボウン(ロック)を持っているスレッドだけが共有リソース(ラン)を変更できます。

条件オブジェクトを使用したPythonスレッドの同期

さて、条件オブジェクトを使用して同期ゲームをさらにレベルアップしましょう。これらは、スレッドのための高度な交通信号機のようなもので、より複雑な調整を可能にします。

以下は、条件オブジェクトを使用したプロデューサー-コンシューマーのシナリオの例です:

import threading
import time
import random

# 共有バッファ
buffer = []
MAX_SIZE = 5

# 条件オブジェクトを作成
condition = threading.Condition()

def producer():
global buffer
while True:
with condition:
while len(buffer) == MAX_SIZE:
print("バッファがいっぱいです、プロデューサーは待機中...")
condition.wait()
item = random.randint(1, 100)
buffer.append(item)
print(f"生成されたアイテム: {item}")
condition.notify()
time.sleep(random.random())

def consumer():
global buffer
while True:
with condition:
while len(buffer) == 0:
print("バッファが空です、コンシューマーは待機中...")
condition.wait()
item = buffer.pop(0)
print(f"消費されたアイテム: {item}")
condition.notify()
time.sleep(random.random())

# プロデューサーとコンシューマーのスレッドを作成
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

# スレッドを開始
producer_thread.start()
consumer_thread.start()

# 少し実行しておきます
time.sleep(10)

この例では、プロデューサーがアイテムをバッファに追加し、コンシューマーがバッファからアイテムを削除しています。条件オブジェクトは、彼らの行動を調整します:

  • プロデューサーはバッファが満タンなときに待機します。
  • コンシューマーはバッファが空のときに待機します。
  • 彼らは安全に進むことができるようにお互いに通知します。

それは、条件オブジェクトが舞台監督であるような美しいダンスです!

join()メソッドを使用したスレッドの同期

join()メソッドは、あるスレッドが舞台に上がる前に他のスレッドがパフォーマンスを終えるのを待つように指示するものです。それは、シンプルで強力なスレッド同期方法です。

以下の例を見てみましょう:

import threading
import time

def worker(name, delay):
print(f"{name} が開始中...")
time.sleep(delay)
print(f"{name} が終了しました!")

# スレッドを作成
thread1 = threading.Thread(target=worker, args=("スレッド1", 2))
thread2 = threading.Thread(target=worker, args=("スレッド2", 4))

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

# thread1が終了するのを待つ
thread1.join()
print("メインスレッドはスレッド1の後に待機中")

# thread2が終了するのを待つ
thread2.join()
print("メインスレッドはスレッド2の後に待機中")

print("すべてのスレッドが終了しました!")

この例では、メインスレッドが2つのワーカースレッドを開始し、それぞれがjoin()を使用して終了を待ちます。それは、親が子供たちが宿題を終えるのを待ってから夕食を提供するのに似ています!

追加のスレッド同期プリミティブ

Pythonは、他にもいくつかのスレッド同期ツールを提供しています。以下のようなものがあります:

プリミティブ 説明 使用例
セマフォ 限定された数のスレッドがリソースにアクセスできるようにする データベース接続のプールを管理する
イベント あるスレッドが他のスレッドにイベントを通知する タスクが完了したことを通知する
バリア 複数のスレッドが特定のポイントに到達するまで待機する レースの開始を同期する

以下は、セマフォを使用した簡単な例です:

import threading
import time

# 2つのスレッドを同時に許可するセマフォを作成
semaphore = threading.Semaphore(2)

def worker(name):
with semaphore:
print(f"{name} がセマフォを獲得しました")
time.sleep(1)
print(f"{name} がセマフォを解放しました")

# 5つのスレッドを作成して開始
threads = []
for i in range(5):
thread = threading.Thread(target=worker, args=(f"スレッド{i}",))
threads.append(thread)
thread.start()

# すべてのスレッドが終了するのを待つ
for thread in threads:
thread.join()

print("すべてのスレッドが終了しました!")

この例では、セマフォはクラブのボウンキャップのように機能し、一度に2つのスレッドだけがアクセスできます。それは、希少なリソースにアクセスを制限する場合に最適です!

それでは、皆さん!Pythonにおけるスレッド同期の素晴らしい世界を探検しました。オーケストラを指揮するか、ダンスを編舞するか、スレッドを同期するのはすべて協調とタイミングのことです。これらのツールをプログラミングキットに加えることで、和音のあるマルチスレッドのPythonプログラムを作成する道を開けます。実践し続け、興味深く、幸せなコーディングを!

Credits: Image by storyset