2.1 モジュール threading
Pythonのマルチスレッドは
複数のスレッド(threads)
を同時に実行する方法で、
主に入力出力操作や並列に実行できるタスクについてプロセッサ資源を効果的に利用できます。
Pythonにおけるマルチスレッドの基本概念:
スレッドは、最小の実行単位であり、同一プロセス内で他のスレッドと並行して動作できます。 同じプロセス内ではすべてのスレッドが共通のメモリを共有し、スレッド間でデータを交換できます。
プロセスは、オペレーティングシステム上で独自のアドレス空間とリソースを持つプログラムのインスタンスです。
スレッドとは異なり、プロセスは互いに隔離され、(IPC)
を通じてデータを交換します。
GIL
は、Pythonのインタプリタにおいて複数のPythonスレッドが同時に実行されるのを防ぐメカニズムです。
GIL
によりPythonコードの実行安全性が確保されますが、多くのCPUコアを持つプロセッサでの
マルチスレッドプログラムのパフォーマンスを制限します。
重要! Global Interpreter Lock (GIL) のため、計算負荷の高いタスクではPythonの マルチスレッドが目立ったパフォーマンス向上をもたらさない場合があります。
モジュール threading
モジュール threading
は高レベルなインターフェースを提供し、
スレッドを作成し管理することができます。スレッドの同期やスレッド間のやりとりを
組織化することができます。ここではこのモジュールの主要なコンポーネントと機能を詳しく見ていきます。
主要なコンポーネント モジュール threading
スレッド操作のエンティティ:
-
Thread
— スレッドの作成と管理のための基本クラス。 -
Timer
— 指定時間後に関数を実行するためのタイマー。 -
ThreadLocal
— スレッドごとのローカルデータを作成するためのもの。
スレッド同期メカニズム:
-
Lock
— 共通資源への同時アクセスを防ぐ同期プリミティブ。 -
Condition
— より複雑なスレッド同期のための条件変数。 Event
— スレッド間の通知のためのプリミティブ。-
Semaphore
— 同時に実行できるスレッドの数を制限するプリミティブ。 -
Barrier
— 指定された数のスレッドを同期し、それらがバリアに到達するまでブロック。
以下ではスレッド操作のための3つのクラスについて説明しますが、スレッド同期メカニズムは すぐには必要ありません。
2.2 クラス Thread
クラス Thread
— スレッドの作成と管理用の基本クラスです。
このクラスには4つの基本メソッドがあります:
start()
: スレッドの実行を開始します。-
join()
: 現在のスレッドは一時停止し、開始したスレッドの終了を待ちます。 is_alive()
: スレッドが実行中の場合はTrue
を返します。-
run()
: スレッドで実行されるコードを含むメソッド。Thread
クラスから継承する際にオーバーライドされます。
クラス Thread
の使用例は、思ったより簡単です。
シンプルなスレッドの起動
import threading
def worker():
print("Worker thread is running")
# 新しいスレッドの作成
t = threading.Thread(target=worker) #新しいスレッドオブジェクトを作成
t.start() #スレッドを開始
t.join() #スレッドの終了を待つ
print("Main thread is finished")
メソッド startを呼び出した後、関数 workerが実行を開始します。 厳密には、そのスレッドがアクティブスレッドのリストに追加されます。
引数の使用
import threading
def worker(number, text):
print(f"Worker {number}: {text}")
# 引数を持つ新しいスレッドの作成
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
新しいスレッドにパラメータを渡すには、タプル形式で指定し、
args
パラメータに割り当てます。指定された関数が呼び出されると、
パラメータが自動的に渡されます。
メソッドrun
のオーバーライド
import threading
class MyThread(threading.Thread):
def run(self):
print("Custom thread is running")
# スレッドの作成と起動
t = MyThread()
t.start()
t.join()
新しいスレッドの実行を開始する関数を指定する方法は2つあります —
Thread
オブジェクトを作成する際にtarget
パラメータを通じて渡すか、
Thread
クラスを継承してrun
関数をオーバーライドするかです。
両方の方法が合法でよく使われています。
2.3 クラス Timer
モジュールthreading
のクラスTimer
は、指定された時間後に
関数を起動するために設計されています。このクラスはマルチスレッド環境で
遅延タスクを実行するのに便利です。
タイマーは、呼び出す関数と、秒単位の遅延時間を指定して作成され、初期化されます。
-
メソッド
start()
はタイマーを起動し、 指定された時間をカウントした後に関数を呼び出します。 -
メソッド
cancel()
は、まだ発動していない場合に タイマーを停止します。これはタイマーがもはや必要でない場合に 関数の実行を防ぐのに便利です。
使用例:
遅延して関数を起動
この例では、関数hello
はタイマーが起動されてから5秒後に呼び出されます。
import threading
def hello():
print("Hello, world!")
# 5秒後に関数helloを呼び出すタイマーを作成
t = threading.Timer(5.0, hello)
t.start() # タイマーを起動
実行前にタイマーを停止
ここでは、タイマーは関数hello
が実行される前に停止されるため、
何も出力されません。
import threading
def hello():
print("Hello, world!")
# タイマーの作成
t = threading.Timer(5.0, hello)
t.start() # タイマーを起動
# 実行前にタイマーを停止
t.cancel()
引数を持つタイマー
この例では、タイマーは3秒後に関数greet
を呼び出し、
引数"Alice"
を渡します。
import threading
def greet(name):
print(f"Hello, {name}!")
# 引数を持つタイマーの作成
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
クラス Timer
は、特定の時間の後にタスクを実行するために便利です。
ただし、タイマーは実行時間の正確性を保証するものではありません。
これはシステムの負荷やスレッドのスケジューラの動作に依存します。
2.4 クラス ThreadLocal
クラス ThreadLocal
は、スレッドごとに独自のローカルデータを持つ
スレッドを作成するためのものです。これはマルチスレッドアプリケーションで、
各スレッドが自分のデータバージョンを持ち、競合や同期の問題を
避けるために便利です。
ThreadLocal
を使用する各スレッドは独自の独立したデータを持ちます。
ThreadLocal
オブジェクトに保存されたデータは、
各スレッドにとってユニークであり、他のスレッドと共有されません。
これは、例えばWebアプリケーションにおける現在のユーザーや、
データベースとの現在の接続のように、特定のスレッドの文脈でのみ
使用されるデータを保存するのに便利です。
使用例:
基本的な使用法
この例では、各スレッドは自分の名前をローカル変数value
に
割り当て、それを出力します。value
の値は各スレッドにとって
ユニークです。
import threading
# ThreadLocalオブジェクトの作成
local_data = threading.local()
def process_data():
# スレッドローカル変数への値の割り当て
local_data.value = threading.current_thread().name
# スレッドローカル変数へのアクセス
print(f'Value in {threading.current_thread().name}: {local_data.value}')
threads = []
for i in range(5):
t = threading.Thread(target=process_data)
threads.append(t)
t.start()
for t in threads:
t.join()
Webアプリケーションにおけるユーザーデータの保存
この例では、各スレッドが自分のユーザーに対するリクエストを処理します。
user_data.user
の値は各スレッドにとってユニークです。
import threading
# ThreadLocalオブジェクトの作成
user_data = threading.local()
def process_request(user):
# スレッドローカル変数への値の割り当て
user_data.user = user
handle_request()
def handle_request():
# スレッドローカル変数へのアクセス
print(f'Handling request for user: {user_data.user}')
threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
t = threading.Thread(target=process_request, args=(user,))
threads.append(t)
t.start()
for t in threads:
t.join()
これがthreading
ライブラリの3つの最も役立つクラスです。
おそらくこれらを仕事で使うことになるでしょうが、他のクラスはあまり使わないかもしれません。
現在、多くの人は非同期関数やライブラリ asyncio
に移行しています。
ですので、これからしばらくはそれについて話します。
GO TO FULL VERSION