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 类

例

import threading

class MyThread(threading.Thread):
    def run(self):
        print("スレッド実行開始")
        # ここにスレッドが実行するコードを記述します
        print("スレッド実行終了")

# スレッドインスタンスを作成
thread = MyThread()
# スレッドを起動
thread.start()
# スレッドの実行が完了するまで待機
thread.join()
print("メインスレッド終了")

方法2:使用threading.Threadコンストラクタ

例

import threading

def my_function():
    print("スレッド実行開始")
    # ここにスレッドが実行するコードを記述します
    print("スレッド実行終了")

# スレッドインスタンスを作成
thread = threading.Thread(target=my_function)
# スレッドを起動
thread.start()
# スレッドの実行が完了するまで待機
thread.join()
print("メインスレッド終了")

2. スレッド同期

マルチスレッドプログラミングでは、複数のスレッドが同時に共有リソースにアクセスする可能性があり、データの不整合の問題を引き起こす可能性があります。これを回避するために、ロック(Lock)。

例

import threading

# ロックオブジェクトを作成
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 threading
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. 基本スレッド作成

例

import threading

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. ロックを使用した共有リソースの保護

例

lock = threading.Lock()
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. イベント同期

例

event = threading.Event()

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)

例

import random
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()

注意事項

  1. グローバルインタプリタロック(GIL):Python の GIL により、同時に1つのスレッドしか Python バイトコードを実行できません。そのため、CPU 密集型タスクでは、マルチスレッドによるパフォーマンス向上は期待できない場合があります。I/O 密集型タスクでは、マルチスレッドは依然として有益です。

  2. スレッドセーフ:マルチスレッド環境では、共有リソースへのアクセスがスレッドセーフであることを保証し、データ競合やデッドロックを回避します。

  3. スレッド数:スレッドを作成しすぎるとシステムリソースが枯渇し、プログラムのパフォーマンスに影響を与える可能性があります。スレッド数を適切に制御するか、スレッドプール(ThreadPoolExecutor)を使用してスレッドを管理します。


小テスト

threading モジュール知識の小テスト

問題 1
1. Python のグローバルインタプリタロック (GIL) は、主に次のどのタイプのマルチスレッドタスクのパフォーマンスに影響しますか?
問題 2
2. スレッドオブジェクトを作成した後、スレッドの実行を開始するにはどのメソッドを呼び出すべきですか?
問題 3
3. 複数のスレッドが同じグローバル変数を変更する必要がある場合、データの混乱を避けるために最も使用すべきものは何ですか?
問題 4
4. メインスレッドが thread.join() を使用する目的は何ですか?
その他の拡張