有没有办法终止一个线程?
有没有办法在不设置或检查任何标志、信号量等的情况下,直接结束一个正在运行的线程呢?
31 个回答
没有官方的接口可以做到这一点。
你需要使用平台提供的接口来结束线程,比如 pthread_kill 或 TerminateThread。你可以通过 pythonwin 或 ctypes 来访问这些接口。
要注意,这样做本身是不安全的。如果你强行结束一个线程,可能会导致一些无法回收的垃圾(比如那些变成垃圾的局部变量),还可能会造成死锁,特别是当被结束的线程在被杀掉时持有全局解释器锁(GIL)。
一个 multiprocessing.Process
可以通过 p.terminate()
来结束。
在我想要结束一个线程,但又不想使用标志、锁、信号、信号量、事件等复杂东西的时候,我会把线程升级为完整的进程。对于只使用几个线程的代码来说,这样的开销并不是很大。
比如,这种方法很方便,可以轻松结束那些执行阻塞输入输出的辅助“线程”。
转换过程很简单:在相关的代码中,把所有的 threading.Thread
替换为 multiprocessing.Process
,把所有的 queue.Queue
替换为 multiprocessing.Queue
,然后在想要结束子进程 p
的父进程中添加必要的 p.terminate()
调用。
可以查看 Python 的 multiprocessing
文档 了解更多信息。
示例:
import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
# Terminate the process
proc.terminate() # sends a SIGTERM
在Python以及其他编程语言中,突然终止一个线程通常不是个好主意。想想以下几种情况:
- 线程正在使用一个重要的资源,这个资源需要正确关闭。
- 这个线程还创建了其他几个线程,这些线程也需要被终止。
如果你能控制线程的管理,处理这个问题的好方法是设置一个退出请求标志,让每个线程定期检查一下,看看是否该退出了。
举个例子:
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 )
(基于Tomer Filiba的可杀线程。关于 PyThreadState_SetAsyncExc
返回值的引用似乎来自于一个旧版本的Python。)
正如文档中提到的,这并不是万能的解决方案,因为如果线程在Python解释器外部忙碌,它将无法捕捉到中断。
使用这段代码的一个好方法是让线程捕捉一个特定的异常并进行清理。这样,你可以中断一个任务,同时确保有适当的清理工作。