フラグやセマフォなどを設定/チェックせずに実行中のスレッドを終了することは可能ですか?
ベストアンサー1
Python に限らず、どの言語でも、突然スレッドを強制終了するのは一般的に悪いパターンです。次のケースを考えてみましょう。
- スレッドは、適切に閉じる必要がある重要なリソースを保持しています。
- このスレッドは、同様に終了する必要がある他のスレッドをいくつか作成しました。
これを処理する良い方法は、余裕がある場合 (独自のスレッドを管理している場合)、各スレッドが定期的にチェックして終了するタイミングかどうかを確認する exit_request フラグを設定することです。
例えば:
import threading
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self, *args, **kwargs):
super(StoppableThread, self).__init__(*args, **kwargs)
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
このコードでは、stop()
スレッドを終了するときに を呼び出し、 を使用してスレッドが適切に終了するまで待機する必要がありますjoin()
。スレッドは、一定の間隔で停止フラグをチェックする必要があります。
ただし、スレッドを強制終了する必要がある場合もあります。例としては、長時間の呼び出しでビジー状態になっている外部ライブラリをラップしていて、それを中断したい場合などが挙げられます。
次のコードでは、(いくつかの制限付きで)Python スレッドで例外を発生させることができます。
def _async_raise(tid, exctype):
'''Raises an exception in the threads with id tid'''
if not inspect.isclass(exctype):
raise TypeError("Only types can be raised (not instances)")
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# "if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
raise SystemError("PyThreadState_SetAsyncExc failed")
class ThreadWithExc(threading.Thread):
'''A thread class that supports raising an exception in the thread from
another thread.
'''
def _get_my_tid(self):
"""determines this (self's) thread id
CAREFUL: this function is executed in the context of the caller
thread, to get the identity of the thread represented by this
instance.
"""
if not self.is_alive(): # Note: self.isAlive() on older version of Python
raise threading.ThreadError("the thread is not active")
# do we have it cached?
if hasattr(self, "_thread_id"):
return self._thread_id
# no, look for it in the _active dict
for tid, tobj in threading._active.items():
if tobj is self:
self._thread_id = tid
return tid
# TODO: in python 2.6, there's a simpler way to do: self.ident
raise AssertionError("could not determine the thread's id")
def raise_exc(self, exctype):
"""Raises the given exception type in the context of this thread.
If the thread is busy in a system call (time.sleep(),
socket.accept(), ...), the exception is simply ignored.
If you are sure that your exception should terminate the thread,
one way to ensure that it works is:
t = ThreadWithExc( ... )
...
t.raise_exc( SomeException )
while t.isAlive():
time.sleep( 0.1 )
t.raise_exc( SomeException )
If the exception is to be caught by the thread, you need a way to
check that your thread has caught it.
CAREFUL: this function is executed in the context of the
caller thread, to raise an exception in the context of the
thread represented by this instance.
"""
_async_raise( self._get_my_tid(), exctype )
(に基づく削除可能なスレッドトメル・フィリバ著。の戻り値に関する引用はPyThreadState_SetAsyncExc
、Pythonの古いバージョン。
ドキュメントに記載されているように、スレッドが Python インタープリターの外部でビジー状態の場合、割り込みをキャッチできないため、これは魔法の弾丸ではありません。
このコードの適切な使用パターンは、スレッドに特定の例外をキャッチさせてクリーンアップを実行させることです。こうすることで、タスクを中断しても適切なクリーンアップを実行できます。