Python threading モジュール
Python のthreadingモジュールは、マルチスレッドプログラミングを実現するための標準ライブラリの1つです。マルチスレッドにより、プログラムは同じ時間内に複数のタスクを実行でき、プログラムの効率と応答速度を向上させます。
threadingモジュールは、スレッドを作成および管理するためのツールを提供し、開発者が簡単に並行プログラムを記述できるようにします。
スレッドとは?
スレッドをオフィスの従業員として想像してみてください:
-
シングルスレッドプログラムは、従業員が1人だけのオフィスのようなもので、その従業員はドキュメントの印刷、メールの返信、コーヒーを淹れるなど、すべての作業を順番に完了しなければなりません。
-
一方、マルチスレッドプログラムは複数の従業員がいるようなもので、彼らは同時に異なるタスクを実行でき、作業効率が大幅に向上します。
コンピュータサイエンスにおいて:
- プロセス:実行中のプログラムであり、独立したメモリ空間を持ちます(例えば、同時に開いているブラウザと音楽プレイヤーは2つのプロセスです)。
- スレッド:プロセス内の独立した実行フローであり、CPUスケジューリングの基本単位です。同じプロセス内のすべてのスレッドはそのプロセスのメモリ空間を共有します(グローバル変数など)。
なぜマルチスレッドを使うのか?
シングルスレッドプログラムでは、タスクは1つずつ順番に実行されます。あるタスクが待機を必要とする場合(例:ネットワーク応答やファイル読み取りの待機)、そのタスクが完了するまでプログラム全体がブロックされます。一方、マルチスレッドでは、あるタスクを待っている間に他のタスクを実行し続けることができるため、プログラム全体のパフォーマンスが向上します。
Python のスレッドとグローバルインタプリタロック (GIL)
Python には、グローバルインタプリタロック(Global Interpreter Lock、GIL)と呼ばれるメカニズムがあります。GIL により、任意の時点で1つのスレッドだけが Python バイトコードを実行できることが保証されます。
これは何を意味するのでしょうか?CPU バウンドなタスク(例:科学計算、画像処理)の場合、GIL の存在により、マルチスレッドでは通常マルチコアの利点を利用して計算速度を向上させることができず、スレッド切り替えのオーバーヘッドのために遅くなることさえあります。
では、Python マルチスレッドの活躍の場はどこにあるのでしょうか?I/O バウンドなタスク(例:ネットワークリクエスト、ファイルの読み書き、ユーザー入力の待機)の場合、スレッドは I/O 操作の完了を待っている間に GIL を解放するため、他のスレッドを実行させることができます。これにより、プログラム全体の応答速度と効率が大幅に向上します。ウェブページの応答を待っている間に、プログラムは別のタスクを処理できるからです。
threading モジュールの使い方?
使用threadingモジュールの最初のステップは、それをインポートすることです:
import threading import time # 用于模拟耗时操作
スレッドを作成する最も基本的な方法は、threading.Threadクラスを使用することです。
構文の説明:
thread_obj = threading.Thread(target=函数名, args=(参数元组,))
- target:スレッド起動後に実行する関数を指定します。
- args:target 関数に渡す引数で、タプル型である必要があります。引数が1つだけの場合は、
(パラメータ,)の形式で記述する必要があります。
1. スレッドの作成
Python では、継承することでthreading.Threadクラスを使用するか、直接threading.Threadコンストラクタを使用してスレッドを作成できます。
方法1:継承threading.Thread 类
例
class MyThread(threading.Thread):
def run(self):
print("スレッド実行開始")
# ここにスレッドが実行するコードを記述します
print("スレッド実行終了")
# スレッドインスタンスを作成
thread = MyThread()
# スレッドを起動
thread.start()
# スレッドの実行が完了するまで待機
thread.join()
print("メインスレッド終了")
方法2:使用threading.Threadコンストラクタ
例
def my_function():
print("スレッド実行開始")
# ここにスレッドが実行するコードを記述します
print("スレッド実行終了")
# スレッドインスタンスを作成
thread = threading.Thread(target=my_function)
# スレッドを起動
thread.start()
# スレッドの実行が完了するまで待機
thread.join()
print("メインスレッド終了")
2. スレッド同期
マルチスレッドプログラミングでは、複数のスレッドが同時に共有リソースにアクセスする可能性があり、データの不整合の問題を引き起こす可能性があります。これを回避するために、ロック(Lock)。
例
# ロックオブジェクトを作成
lock = threading.Lock()
def my_function():
with lock:
print("スレッド実行開始")
# ここにスレッドが実行するコードを記述します
print("スレッド実行終了")
# スレッドインスタンスを作成
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# スレッドを起動
thread1.start()
thread2.start()
# スレッドの実行が完了するまで待機
thread1.join()
thread2.join()
print("メインスレッド終了")
3. スレッド間通信
スレッド間通信はキュー(Queue)を使用して実現できます。Queueはスレッドセーフであり、複数のスレッド間で安全にデータを渡すことができます。
例
import queue
def worker(q):
while not q.empty():
item = q.get()
print(f"項目を処理中: {item}")
q.task_done()
# キューを作成してデータを投入
q = queue.Queue()
for i in range(10):
q.put(i)
# スレッドインスタンスを作成
thread1 = threading.Thread(target=worker, args=(q,))
thread2 = threading.Thread(target=worker, args=(q,))
# スレッドを起動
thread1.start()
thread2.start()
# キュー内のすべての項目が処理されるまで待機
q.join()
print("すべての項目の処理が完了しました")
よく使うクラス、メソッド、属性
1. 主要クラス
| クラス/メソッド/属性 | 説明 | 例 |
|---|---|---|
threading.Thread | スレッドクラス。スレッドの作成と管理に使用します | t = Thread(target=func, args=(1,)) |
threading.Lock | ミューテックスロック(プリミティブロック) | lock = Lock() |
threading.RLock | 再入可能ロック(同じスレッドが複数回取得可能) | rlock = RLock() |
threading.Event | イベントオブジェクト。スレッド同期に使用します | event = Event() |
threading.Condition | 条件変数。複雑なスレッド調整に使用します | cond = Condition() |
threading.Semaphore | セマフォ。並行スレッド数を制御します | sem = Semaphore(3) |
threading.BoundedSemaphore | 有界セマフォ(カウントが初期値を超えないようにします) | b_sem = BoundedSemaphore(2) |
threading.Timer | タイマースレッド。遅延実行します | timer = Timer(5.0, func) |
threading.local | スレッドローカルデータ(各スレッドが独立して保存) | local_data = threading.local() |
2. Thread オブジェクトのよく使うメソッド/属性
| メソッド/属性 | 説明 | 例 |
|---|---|---|
start() | スレッドを起動 | t.start() |
run() | スレッドが実行するメソッド(オーバーライド可能) | カスタムクラスを作成する際にこのメソッドをオーバーライド |
join(timeout=None) | 現在のスレッドをブロックし、対象スレッドが終了するまで待機 | t.join() |
is_alive() | スレッドが実行中かどうかを確認 | if t.is_alive(): |
name | スレッド名(変更可能) | t.name = "Worker-1" |
daemon | デーモンスレッドフラグ(メインスレッド終了時に自動終了) | t.daemon = True |
ident | スレッド識別子(未起動の場合はNone) | print(t.ident) |
3. Lock/RLock のよく使うメソッド
| メソッド | 説明 | 例 |
|---|---|---|
acquire(blocking=True, timeout=-1) | ロックを取得(ブロックあり/なし) | lock.acquire() |
release() | ロックを解放 | lock.release() |
locked() | ロックが占有されているか確認 | if not lock.locked(): |
4. Event のよく使うメソッド
| メソッド | 説明 | 例 |
|---|---|---|
set() | イベントを真に設定し、待機中のすべてのスレッドを起動 | event.set() |
clear() | イベントを偽にリセット | event.clear() |
wait(timeout=None) | イベントが真になるかタイムアウトするまでブロック | event.wait(2.0) |
is_set() | イベントの状態を確認 | if event.is_set(): |
5. Condition のよく使うメソッド
| メソッド | 説明 | 例 |
|---|---|---|
wait(timeout=None) | ロックを解放してブロックし、通知を受けるかタイムアウトするまで待機 | cond.wait() |
notify(n=1) | 最大n個の待機スレッドを起こす | cond.notify(2) |
notify_all() | 待機中のすべてのスレッドを起こす | cond.notify_all() |
6. モジュールレベルの関数/属性
| 関数/属性 | 説明 | 例 |
|---|---|---|
threading.active_count() | 現在アクティブなスレッド数を返す | print(threading.active_count()) |
threading.current_thread() | 現在のスレッドオブジェクトを返す | print(threading.current_thread().name) |
threading.enumerate() | アクティブなすべてのスレッドのリストを返す | for t in threading.enumerate(): |
threading.main_thread() | メインスレッドオブジェクトを返す | if threading.current_thread() is threading.main_thread(): |
threading.get_ident() | 現在のスレッドの識別子を返す(Python 3.3+) | print(threading.get_ident()) |
例
1. 基本スレッド作成
例
def worker(num):
print(f"Worker {num} started")
threads = []
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
2. ロックを使用した共有リソースの保護
例
count = 0
def increment():
global count
with lock: # ロックの自動取得と解放
count += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(count) # 出力: 10
3. イベント同期
例
def waiter():
print("Waiting for event...")
event.wait()
print("Event triggered!")
t = threading.Thread(target=waiter)
t.start()
# メインスレッドがイベントをトリガー
threading.Event().wait(2.0) # 遅延をシミュレート
event.set()
t.join()
4. プロデューサー・コンシューマーモデル(Condition)
例
from threading import Condition
queue = []
cond = Condition()
MAX_ITEMS = 5
def producer():
for _ in range(10):
with cond:
while len(queue) >= MAX_ITEMS:
cond.wait()
item = random.randint(1, 100)
queue.append(item)
print(f"Produced {item}")
cond.notify()
def consumer():
for _ in range(10):
with cond:
while not queue:
cond.wait()
item = queue.pop(0)
print(f"Consumed {item}")
cond.notify()
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
注意事項
グローバルインタプリタロック(GIL):Python の GIL により、同時に1つのスレッドしか Python バイトコードを実行できません。そのため、CPU 密集型タスクでは、マルチスレッドによるパフォーマンス向上は期待できない場合があります。I/O 密集型タスクでは、マルチスレッドは依然として有益です。
スレッドセーフ:マルチスレッド環境では、共有リソースへのアクセスがスレッドセーフであることを保証し、データ競合やデッドロックを回避します。
スレッド数:スレッドを作成しすぎるとシステムリソースが枯渇し、プログラムのパフォーマンスに影響を与える可能性があります。スレッド数を適切に制御するか、スレッドプール(
ThreadPoolExecutor)を使用してスレッドを管理します。