threading.Thread()
| 対応: | Python 2(2000) |
|---|
スレッドを使うと、複数の処理を同時に実行できます。例えば、ファイルのダウンロードを待つ間に別の処理を進めるといった使い方ができます。
『threading』モジュールはスレッドを使って処理を並行実行するための標準ライブラリです。複数のスレッドが同時に動作し、I/O待ち(ファイル読み書き・ネットワーク通信など)の間に他の処理を進められます。PythonのGIL(グローバルインタープリタロック)により、CPUを多用する計算処理は真の並列実行にはなりません。CPU密集処理にはmultiprocessingを使う方法があります。
構文
import threading
# スレッドの生成と実行
t = threading.Thread(target=関数, args=(引数,))
t.start() # スレッド開始
t.join() # スレッドの終了を待つ
# 排他制御(Lock)
lock = threading.Lock()
with lock:
# クリティカルセクション
pass
クラス・メソッド一覧
| クラス・メソッド | 概要 |
|---|---|
| Thread(target, args, kwargs) | スレッドオブジェクトを生成する。 |
| t.start() | スレッドを開始する。 |
| t.join(timeout=None) | スレッドが終了するまでメインスレッドをブロックする。 |
| t.is_alive() | スレッドがまだ実行中かどうかを返す。 |
| t.daemon = True | デーモンスレッドに設定する。メインが終了すると自動で終了する。 |
| threading.Lock() | 排他制御のためのロックオブジェクトを生成する。 |
| lock.acquire() | ロックを取得する(withで書く方法もある)。 |
| lock.release() | ロックを解放する(withで書く方法もある)。 |
| threading.current_thread() | 現在実行中のスレッドオブジェクトを返す。 |
サンプルコード
threading_basic.py
import threading
import time
def download(url, name):
print(f"[{name}] ダウンロード開始: {url}")
time.sleep(2)
print(f"[{name}] ダウンロード完了!")
urls = ['https://example.com/file1', 'https://example.com/file2', 'https://example.com/file3']
threads = []
for i, url in enumerate(urls):
t = threading.Thread(target=download, args=(url, f"Thread-{i+1}"))
threads.append(t)
t.start()
for t in threads:
t.join()
print("すべてのダウンロードが完了しました")
実行すると次のように出力されます。
python3 threading_basic.py [Thread-1] ダウンロード開始: https://example.com/file1 [Thread-2] ダウンロード開始: https://example.com/file2 [Thread-3] ダウンロード開始: https://example.com/file3 [Thread-1] ダウンロード完了! [Thread-2] ダウンロード完了! [Thread-3] ダウンロード完了! すべてのダウンロードが完了しました
threading_lock.py
import threading
counter = 0
lock = threading.Lock()
def increment(n):
global counter
for _ in range(n):
with lock:
counter += 1
threads = [threading.Thread(target=increment, args=(10000,)) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"カウンタ: {counter}")
実行すると次のように出力されます。
python3 threading_lock.py カウンタ: 50000
threading_subclass.py
import threading
import time
class WorkerThread(threading.Thread):
def __init__(self, task_id):
super().__init__(daemon=True)
self.task_id = task_id
self.result = None
def run(self):
time.sleep(1)
self.result = self.task_id ** 2
workers = [WorkerThread(i) for i in range(5)]
for w in workers: w.start()
for w in workers: w.join()
print([w.result for w in workers])
実行すると次のように出力されます。
python3 threading_subclass.py [0, 1, 4, 9, 16]
よくあるミス
よくあるミス1: Lockなしで共有変数を更新する
複数のスレッドが同時に共有変数を読み書きすると、レースコンディション(競合状態)が発生し、値がずれることがあります。共有リソースへのアクセスは必ずLockで保護します。
import threading
counter = 0
def increment(n):
global counter
for _ in range(n):
counter += 1
threads = [threading.Thread(target=increment, args=(10000,)) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"カウンタ: {counter}")
次のように記述します。
import threading
counter = 0
lock = threading.Lock()
def increment(n):
global counter
for _ in range(n):
with lock:
counter += 1
threads = [threading.Thread(target=increment, args=(10000,)) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"カウンタ: {counter}")
よくあるミス2: スクリプト名をthreading.pyにする
スクリプトのファイル名を『threading.py』にすると、標準ライブラリの『threading』モジュールと名前が衝突し、importが失敗します。サンプルファイル名には『_example』や『_basic』などの接尾辞を付けると衝突を避けられます。
import threading
t = threading.Thread(target=print, args=("hello",))
t.start()
threading_basic.py
import threading
t = threading.Thread(target=print, args=("hello",))
t.start()
t.join()
概要
スレッドが共有リソース(グローバル変数・ファイルなど)にアクセスするとき、排他制御(Lock)がないとレースコンディション(競合状態)が発生します。レースコンディションとは、複数のスレッドが同時にデータにアクセスすることで予期しない結果になる問題です。複数のスレッドが同時に読み書きを行うと、予測できない結果になる可能性があります。
PythonのGIL(グローバルインタープリタロック)はPythonオブジェクトへのアクセスをスレッドセーフにするための仕組みですが、同時に1つのスレッドしかPythonコードを実行できないことも意味します。そのため数値計算・画像処理などのCPU密集タスクには効果がなく、ファイルI/O・HTTPリクエストなどのI/O待ちタスクに有効です。CPU密集タスクの並列化にはmultiprocessingを使う方法があります。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。