この記事では、Pythonのthreading
モジュールを使い、プログラム内で複数の処理を同時に(並行して)実行するためのマルチスレッド処理の基本について解説します。
シングルスレッドの問題点
通常のPythonプログラムは、書かれた順に一行ずつ処理を実行するシングルスレッドで動作します。そのため、時間のかかる処理(例えば、time.sleep()
による待機や、大規模なファイルのダウンロードなど)があると、その処理が終わるまでプログラム全体が停止してしまいます。
import time
print("処理を開始します。")
# 5秒かかる重い処理をシミュレート
time.sleep(5)
print("処理が完了しました。")
このコードでは、time.sleep(5)
が実行されている5秒間、プログラムは何もできずに固まってしまいます。
threading
モジュールによるマルチスレッド処理
threading
モジュールを使うと、時間のかかる処理をバックグラウンドの別のスレッド
で実行させ、その間にメインのプログラムは別の作業を続けることができます。
基本的な手順は以下の通りです。
threading.Thread()
で新しいスレッドオブジェクトを作成し、target
引数に実行したい関数を指定する。- スレッドオブジェクトの
start()
メソッドを呼び出す。
import threading
import time
print("メインプログラム開始")
def heavy_task():
print(" サブスレッド: 5秒かかる処理を開始...")
time.sleep(5)
print(" サブスレッド: 処理完了!")
# heavy_taskをターゲットとして新しいスレッドを作成
thread_obj = threading.Thread(target=heavy_task)
# スレッドを開始
thread_obj.start()
print("メインプログラムは、サブスレッドの完了を待たずに終了します。")
このコードを実行すると、「メインプログラム開始」と「メインプログラムは…終了します。」がほぼ同時に表示され、その5秒後にサブスレッドのメッセージが表示されます。メインプログラムがtime.sleep()
でブロックされていないことがわかります。
スレッド関数への引数渡し
threading.Thread()
を生成する際に、args
引数(タプルまたはリスト)とkwargs
引数(辞書)を使って、ターゲット関数に引数を渡すことができます。
import threading
import time
def worker(name, duration):
print(f" スレッド {name}: 開始")
time.sleep(duration)
print(f" スレッド {name}: 終了")
# ターゲット関数workerに引数を渡してスレッドを作成
thread_obj = threading.Thread(target=worker, args=['TaskA', 2])
thread_obj.start()
print("メインプログラムは処理を続行します。")
並行処理の問題点
複数のスレッドを同時に実行するのは簡単ですが、注意も必要です。もし複数のスレッドが、同じ変数を同時に読み書きしようとすると、互いに干渉し合い、予期せぬ結果を引き起こす可能性があります。これを競合状態
(race condition)と呼びます。
例えば、あるスレッドが変数を読み取った直後に、別のスレッドがその変数を書き換えてしまうと、最初のスレッドは古いデータに基づいて処理を進めてしまうかもしれません。このような並行処理に起因する問題は、発生するタイミングが予測しにくいため、デバッグが非常に困難になることがあります。
まとめ
threading
モジュールは、時間のかかるI/Oバウンドなタスク(ファイルの読み書き、ネットワーク通信など)をバックグラウンドで実行させ、アプリケーションの応答性を維持するのに役立ちます。threading.Thread
で新しいスレッドを生成し、start()
で実行するのが基本です。ただし、複数のスレッドが共有データを扱う際には、意図しない動作を引き起こす競合状態
に注意する必要があります。