Threadの隠れメソッドの話
pythonでは、Threadクラスを継承して独自のスレッドを実装することができます。
そこで、汎用的に使用できるワーカースレッドのベースクラスを作成しました。
内容は大体、以下のような感じです。
Worker.py
import time
from queue import Queue
from threading import Event, Thread
from abc import abstractmethod
class Worker(Thread):
def __init__(self, pipe_name, exit_msg='exit'):
super().__init__()
self._stop = Event()
self._queue = Queue()
# ここに名前付きパイプの初期化処理
self._pipe = PipeWatch(pipe_name, exit_msg=exit_msg, self._queue, self._stop)
# PipeWatchは名前付きパイプを非同期に読みexit_msgが来たら_stop.set()します
# それ以外は、_queueに追加して、本クラスのサブクラスで読み込ませる
@abstractmethod
def onStart(self):
pass# サブクラスはオーバーライドしてFalseを返せばスレッドは終わる
@abstractmethod
def loop(self):
pass# サブクラスはオーバーライドしてFalseを返せばスレッドは終わる
@abstractmethod
def onException(self, ex):
pass# 例外はサブクラスのオーバーライドで処理させる
@abstractmethod
def onClosing(self):
pass# 閉塞はサブクラスのオーバーライドで処理させる
def dequeue(self):
_data = None
if not self._queue.empty():
_data = self._queue()
return _data
def enqueue(self, data):
if self._queue.put(data)
def start(self):
super().start()
self._pipe.start()
def run(self):
try:
if self.onStart() != False:
while not self._stop.is_set():
if self.loop() == False:
break
time.sleep(0.1)
except BaseException as ex:
self.onException(ex)
finally:
self.onClosing()
self._pipe.stop()
このクラスの大体の仕様は、
- スレッドは、異なる.pyファイルに書かれていても、通信が行えるイメージ
- スレッド間の通信には、ソケットは使わず、名前付きパイプを使用する
- 両方が開かれてないとブロックされる名前付きパイプではなく、非ブロッキングモードでオープンし、必要なときに操作を取消できる
このクラスのサブクラスを実装して動かしたところ、期待した動作をするようになりましたが、スレッド終了時に、終了せずにいつまでも終わらないという問題が発生しました。
そして、よく見ると、以下のようなエラーメッセージが出ています。
Terminal
File "/usr/lib/python3.11/threading.py", line 1134, in _wait_for_tstate_lock
self._stop()
TypeError: 'Event' object is not callable
最初は訳が分かりませんでした。
でも、/usr/lib/python3.11/threading.py
を開いてみて原因が分かりました。
pythonのスレッドモジュールには、ドキュメントには書かれていない、
Thread._stop()
というメソッドが存在していたのです。
私のコードには、以下のような記述があります。
Worker.py 抜粋
class Worker(Thread):
def __init__(self, pipe_name, exit_msg='exit'):
super().__init__()
self._stop = Event() # <-これ!これが原因です。
self._queue = Queue()
pythonのthreadingモジュールはスレッドを停止させるときに、
Thread._stop()
を呼んでいたのです。
でも、そこは、私がイベントとして_stop
を使ってしまっていたのです。
だから、
Terminal
TypeError: 'Event' object is not callable
と怒られていたのです。