为什么python的threading.Thread对象有'start'而没有'stop'?
Python的一个模块叫做threading,里面有一个对象叫做Thread
,可以用来在不同的线程中运行进程和函数。这个对象有一个start
方法,可以用来启动线程,但没有stop
方法。那么,为什么Thread
不能通过简单调用stop
方法来停止呢?我能想象有些情况下使用join
方法会很不方便……
5 个回答
安全地结束线程并不简单。想想看,结束线程后需要做哪些清理工作:哪些锁(可能和其他线程共享的锁)需要自动释放?如果不处理好这些,就很容易出现死锁的情况!
更好的方法是自己实现一个合适的关闭机制,然后设置
mythread.shutdown = True
mythread.join()
来停止线程。
当然,你的线程应该做一些事情,比如
while not this.shutdown:
continueDoingSomething()
releaseThreadSpecificLocksAndResources()
来频繁检查关闭标志。或者,你也可以依赖操作系统特定的信号机制来中断线程,捕捉到这个中断后再进行清理。
这里的清理是最重要的部分!
确实可以实现一个 Thread.stop
方法,下面的示例代码展示了这一点:
import threading
import sys
class StopThread(StopIteration): pass
threading.SystemExit = SystemExit, StopThread
class Thread2(threading.Thread):
def stop(self):
self.__stop = True
def _bootstrap(self):
if threading._trace_hook is not None:
raise ValueError('Cannot run thread with tracing!')
self.__stop = False
sys.settrace(self.__trace)
super()._bootstrap()
def __trace(self, frame, event, arg):
if self.__stop:
raise StopThread()
return self.__trace
class Thread3(threading.Thread):
def _bootstrap(self, stop_thread=False):
def stop():
nonlocal stop_thread
stop_thread = True
self.stop = stop
def tracer(*_):
if stop_thread:
raise StopThread()
return tracer
sys.settrace(tracer)
super()._bootstrap()
################################################################################
import time
def main():
test = Thread2(target=printer)
test.start()
time.sleep(1)
test.stop()
test.join()
def printer():
while True:
print(time.time() % 1)
time.sleep(0.1)
if __name__ == '__main__':
main()
Thread3
类的运行速度看起来比 Thread2
类快大约 33%。
补充说明:
如果对 Python 的 C API 有足够的了解,并且会使用 ctypes
模块,就可以写出一种更高效的方式来在需要时停止一个线程。使用 sys.settrace
的问题在于,跟踪函数会在每条指令后运行。如果在需要终止的线程上抛出一个异步异常,就不会影响执行速度。下面的代码在这方面提供了一些灵活性:
#! /usr/bin/env python3
import _thread
import ctypes as _ctypes
import threading as _threading
_PyThreadState_SetAsyncExc = _ctypes.pythonapi.PyThreadState_SetAsyncExc
# noinspection SpellCheckingInspection
_PyThreadState_SetAsyncExc.argtypes = _ctypes.c_ulong, _ctypes.py_object
_PyThreadState_SetAsyncExc.restype = _ctypes.c_int
# noinspection PyUnreachableCode
if __debug__:
# noinspection PyShadowingBuiltins
def _set_async_exc(id, exc):
if not isinstance(id, int):
raise TypeError(f'{id!r} not an int instance')
if not isinstance(exc, type):
raise TypeError(f'{exc!r} not a type instance')
if not issubclass(exc, BaseException):
raise SystemError(f'{exc!r} not a BaseException subclass')
return _PyThreadState_SetAsyncExc(id, exc)
else:
_set_async_exc = _PyThreadState_SetAsyncExc
# noinspection PyShadowingBuiltins
def set_async_exc(id, exc, *args):
if args:
class StateInfo(exc):
def __init__(self):
super().__init__(*args)
return _set_async_exc(id, StateInfo)
return _set_async_exc(id, exc)
def interrupt(ident=None):
if ident is None:
_thread.interrupt_main()
else:
set_async_exc(ident, KeyboardInterrupt)
# noinspection PyShadowingBuiltins
def exit(ident=None):
if ident is None:
_thread.exit()
else:
set_async_exc(ident, SystemExit)
class ThreadAbortException(SystemExit):
pass
class Thread(_threading.Thread):
def set_async_exc(self, exc, *args):
return set_async_exc(self.ident, exc, *args)
def interrupt(self):
self.set_async_exc(KeyboardInterrupt)
def exit(self):
self.set_async_exc(SystemExit)
def abort(self, *args):
self.set_async_exc(ThreadAbortException, *args)
start
这个词可以用得很广泛,因为它只是启动了线程的目标。但是,stop
这个词如果也用得很广泛,那会是什么样呢?这要看你的线程在做什么。如果你的线程在处理网络连接、释放系统资源、保存文件或其他一些复杂的任务,那么你可能需要做很多事情来“停止”它。要是有一个系统能以通用的方式处理这些事情,那每个线程的负担就会变得很重,效率也会下降,而且会变得非常复杂,充满了各种特殊情况,几乎让人无法使用。不过,你可以在主线程中不使用join
来跟踪所有创建的线程,然后检查它们的运行状态,当主线程关闭时,给它们发送一个终止消息。