工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Industrial programming >> Python

Pythonマルチスレッド完全ガイド:GIL解説と実践例

Pythonにはワークロードを高速化するためのマルチプロセッシングとマルチスレッドの両方が用意されています。このガイドでは、堅牢なスレッドベースアプリケーションの作成、グローバルインタプリタロック(GIL)のニュアンス、デッドロックや競合状態といった一般的な落とし穴の回避方法を解説します。

スレッドとは

スレッドは単一プロセス内の軽量な実行単位です。スレッドはプロセスのメモリ空間を共有し、効率的なデータ交換を可能にしながら、OSによって1つ以上のCPUコア上で並行スケジュールされます。

プロセスとは

プロセスは独立したプログラムインスタンスです。ブラウザやテキストエディタなどのアプリケーションを起動すると、OSは独自のコード、データ、メモリマップを持つ別プロセスを作成します。

Pythonマルチスレッドの基礎

Pythonのthreadingモジュールはスレッド管理の高レベルAPIを提供し、レガシーな_threadモジュールは非推奨で後方互換性のためのみ残されています。スレッドはI/Oバウンドタスクに適し、CPUバウンドな作業はmultiprocessingモジュールを使ってGILを回避すべきです。

マルチスレッドを使うべき場面

ネットワークリクエスト、ファイルI/O、データベース呼び出しなど、複数のI/O負荷の高い操作を同時に実行する必要があるときにマルチスレッドが輝きます。適切に設計されたスレッドは、新しいプロセスを生成するオーバーヘッドなしで応答性とスループットを向上させます。

スレッド用主要Pythonモジュール

  1. _thread – 低レベル、非推奨。
  2. threading – モダン、機能豊富、推奨される選択。

threadingモジュールの使用

threadingモジュールはThreadクラス、同期プリミティブ(Lock、RLock、Semaphore、Condition、Event、Barrier)、activeCount()やenumerate()などのユーティリティ関数を提供。以下はスレッド作成と同期を示す最小例。

import time
import _thread
def thread_test(name, wait):
    for i in range(4):
        time.sleep(wait)
        print(f"Running {name}")
    print(f"{name} has finished execution")
if __name__ == "__main__":
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

このコードを実行すると、インターリーブされた出力が並行実行を示します。

Pythonマルチスレッド完全ガイド:GIL解説と実践例

解説

  1. timeとレガシー_threadモジュールをインポート。
  2. thread_testを定義し、作業を実行して進捗をログ出力。
  3. start_new_threadで3つの独立スレッドを起動。

モダンなスレッド:threading.Thread

実践ではthreading.Threadをサブクラス化し、run()メソッドをオーバーライドします。以下の例は前回の振る舞いを高レベルAPIで再現します。

import time
import threading
class ThreadTester(threading.Thread):
    def __init__(self, name, delay):
        super().__init__()
        self.name = name
        self.delay = delay
    def run(self):
        for _ in range(4):
            time.sleep(self.delay)
            print(f"Running {self.name}")
        print(f"{self.name} has finished execution")
if __name__ == "__main__":
    threads = [
        ThreadTester("First Thread", 1),
        ThreadTester("Second Thread", 2),
        ThreadTester("Third Thread", 3),
    ]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

出力:

Pythonマルチスレッド完全ガイド:GIL解説と実践例

解説

  1. Threadをサブクラス化してスレッド振る舞いをカプセル化。
  2. __init__をオーバーライドしてスレッド固有データを保持。
  3. runをオーバーライドしてタスクを定義。
  4. インスタンス化、開始、各スレッドをjoin。

よくあるスレッドの落とし穴

デッドロック

デッドロックは、2つ以上のスレッドが互いに保持するリソースを無限に待ち合う状態です。古典的な例:食事する哲学者問題—5人の哲学者が5本のフォークを奪い合い、それぞれ食べるには2本必要。全員が右のフォークを先に取ると循環待ちが発生し、全員が停止します。

競合状態

複数スレッドが適切な調整なしに共有データを変更すると、最終状態が予測不能になります。例:

i = 0
for _ in range(100):
    print(i)
    i += 1

このループを並列スレッドで実行すると、インクリメントが重なり合ってスクランブルしたシーケンスが生成される可能性があります。

スレッドの同期

threadingモジュールはいくつかの同期プリミティブを提供。最も基本的なのはLockで、共有リソースへの排他アクセスを保証します。

import threading
lock = threading.Lock()
def worker(name):
    for _ in range(5):
        lock.acquire()
        print(f"{name} acquired lock")
        # クリティカルセクション
        lock.release()
if __name__ == "__main__":
    t1 = threading.Thread(target=worker, args=("Thread A",))
    t2 = threading.Thread(target=worker, args=("Thread B",))
    t1.start(); t2.start()
    t1.join(); t2.join()

出力はクリティカルセクションへの直列化アクセスを示します。

Pythonマルチスレッド完全ガイド:GIL解説と実践例

その他の同期ツール

グローバルインタプリタロック(GIL)の理解

CPythonはGILを用いてスレッド間でのバイトコード実行を直列化します。これはインタプリタの内部構造(参照カウント等)を競合状態から保護しますが、マルチコアシステム上でのCPUバウンドタスクの並列性を制限します。

  1. スレッド開始時にGILを取得。
  2. 別スレッドがGILを必要とすれば、最初のスレッドが解放するまでブロック。
  3. I/Oバウンドスレッドはブロッキング呼び出し中にGILを解放し、他スレッドを実行可能にする。
  4. CPUバウンドスレッドはGILを保持し続け、他スレッドのPythonコード実行を阻害。

したがって、Pythonでのマルチスレッドは計算負荷の高いワークロードではほとんどメリットがなく、そうしたケースではmultiprocessingが推奨されます。

GILが存在する理由

CPythonのガベージコレクタは参照カウントに依存しています。グローバルロックなしで参照カウントを並行更新すると、競合状態とメモリ破損を招きます。オブジェクトごとのロックはオーバーヘッドが大きいため、GILがシンプルで効果的な解決策として採用されました。

実践的なポイント

Python

  1. C言語free()関数完全マスター:実践ガイドと例
  2. C# マルチスレッド完全マスター:パフォーマンスと効率を向上させる
  3. Python strip()メソッド完全ガイド:実践例付き
  4. Python str.count()マスター:文字と部分文字列のカウント実例集
  5. Python round()関数を実例で徹底解説
  6. Python map()関数をマスター:構文、例、ベストプラクティス
  7. Python timeit():実行時間測定の実例付きガイド
  8. Python collections.Counter:効率的なカウント、更新、演算操作
  9. Python list.count() 完全ガイド:実践例付きで徹底解説
  10. Python list.index():要素の位置を検索する実践ガイド
  11. Pythonマルチスレッドプログラミング:効率的な並行処理テクニック