【Python】threadingをクラス化して「バックグラウンド処理」を実装する方法

この記事は約5分で読めます。

Pythonでプログラムを書いていると、「通信の受信を待ち受けながら、同時に別の計算や画面更新を行いたい」という場面に遭遇します。

通常、Pythonは上から下へ順番に実行される(同期処理)ため、通信待ちが発生するとプログラム全体が止まってしまいます。これを解決するのが、threadingモジュールを使ったクラス化です。

本記事では、実戦で役立つ「スレッドを継承したクラスの作成と呼び出し方法」を解説します。

1. なぜ「クラス」をスレッド化するのか?

TCPやUDPの通信、あるいはセンサーの監視など、「いつ来るかわからないデータ」を待つ処理にはスレッドが最適です。

  • ブロック回避: 受信待ち(recvなど)の間も、メインの処理を止めずに済む。
  • カプセル化: 通信設定(IPやポート)や状態管理を一つのクラスに閉じ込められる。
  • 整理されたコード: 「待ち受け担当」と「メイン担当」で役割をハッキリ分けられる。

2. スレッドクラスの実装とメイン側での呼び出し

以下に、スレッドクラスの定義から、メイン側での呼び出しまでをまとめたコード例を示します。

import threading
import time

# --- 1. スレッドを継承したクラスの定義 ---
class NetworkReceiver(threading.Thread):
    """
    バックグラウンドで通信(受信)を待ち続けるクラス
    """
    def __init__(self, target_name):
        super(NetworkReceiver, self).__init__()
        self.target_name = target_name
        self.is_running = True
        self.daemon = True  # メイン終了時にこのスレッドも強制終了させる設定

    def run(self):
        """
        start()を呼ぶと、このメソッドの中身が別スレッドで動き出す
        """
        print(f"  [Thread] {self.target_name}:受信待ちを開始しました...")
        
        while self.is_running:
            # 本来はここで socket.recv() などの通信待ちを行う
            # 今回はデモとして3秒ごとに「データ受信」を表示
            time.sleep(3)
            print(f"\n  [Thread] ★重要★ {self.target_name}からデータを受信しました!")

        print(f"  [Thread] {self.target_name}:スレッドを安全に終了しました。")

    def stop(self):
        """スレッドを外部から止めるためのメソッド"""
        self.is_running = False


# --- 2. メイン側での呼び出し(メインロジック) ---
if __name__ == "__main__":
    print("=== メイン処理:開始 ===")

    # スレッドクラスのインスタンス化
    receiver = NetworkReceiver("TCP-Server")

    # スレッドを開始(内部で run() が呼ばれる)
    receiver.start()

    # メインスレッドでの別の仕事
    try:
        for i in range(1, 11):
            print(f"[Main] 重い計算やUI更新などを実行中... ({i}/10)")
            # スレッド側が受信待ちをしていても、メインは止まらずに進む
            time.sleep(1)

    except KeyboardInterrupt:
        print("\n[Main] ユーザーにより中断されました")

    finally:
        # スレッドを安全に停止させる
        print("=== メイン処理:スレッドに停止命令を出します ===")
        receiver.stop()
        receiver.join()  # スレッドが完全に終わるのを待つ

    print("=== メイン処理:すべて完了しました ===")

3. コードのポイント解説

threading.Thread の継承

class MyClass(threading.Thread): とすることで、そのクラス自体がスレッドの機能を持つようになります。初期化時には必ず super().__init__() を呼びましょう。

run() メソッド

ここが「別動隊」が動くステージです。start() を呼び出すと自動的にこのメソッドが実行されます。通信プログラムの場合、ここを while ループにして受信を待ち続けます。

daemon = True の設定

これをしておかないと、メインのプログラムが終わっても、受信待ちをしているスレッドが終了せずに残り続けてしまう(プロセスが死なない)ことがあります。

④ メイン側の自由

コードを見てわかる通り、receiver.start() を呼んだ直後から、メイン側は自分のループ処理(10秒間のカウント)に入っています。その間、3秒ごとにスレッド側から「受信した!」というメッセージが割り込んできます。これこそが並列処理の醍醐味です。

4. まとめ:同期から非同期へ

Pythonの標準的な書き方では「受信を待っている間は、次の行に進めない」という制約がありますが、スレッドクラスを使えばその壁を越えられます。

TCP/UDP通信を扱うエンジニアにとって、この手法は「メインの制御を止めずに通信を維持する」ための必須スキルと言えるでしょう。

コメント

タイトルとURLをコピーしました