为什么python的threading.Thread对象有'start'而没有'stop'?

4 投票
5 回答
45254 浏览
提问于 2025-04-17 13:25

Python的一个模块叫做threading,里面有一个对象叫做Thread,可以用来在不同的线程中运行进程和函数。这个对象有一个start方法,可以用来启动线程,但没有stop方法。那么,为什么Thread不能通过简单调用stop方法来停止呢?我能想象有些情况下使用join方法会很不方便……

5 个回答

5

安全地结束线程并不简单。想想看,结束线程后需要做哪些清理工作:哪些锁(可能和其他线程共享的锁)需要自动释放?如果不处理好这些,就很容易出现死锁的情况!

更好的方法是自己实现一个合适的关闭机制,然后设置

mythread.shutdown = True
mythread.join()

来停止线程。

当然,你的线程应该做一些事情,比如

while not this.shutdown:
    continueDoingSomething()
releaseThreadSpecificLocksAndResources()

来频繁检查关闭标志。或者,你也可以依赖操作系统特定的信号机制来中断线程,捕捉到这个中断后再进行清理

这里的清理是最重要的部分!

8

确实可以实现一个 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)
10

start这个词可以用得很广泛,因为它只是启动了线程的目标。但是,stop这个词如果也用得很广泛,那会是什么样呢?这要看你的线程在做什么。如果你的线程在处理网络连接、释放系统资源、保存文件或其他一些复杂的任务,那么你可能需要做很多事情来“停止”它。要是有一个系统能以通用的方式处理这些事情,那每个线程的负担就会变得很重,效率也会下降,而且会变得非常复杂,充满了各种特殊情况,几乎让人无法使用。不过,你可以在主线程中不使用join来跟踪所有创建的线程,然后检查它们的运行状态,当主线程关闭时,给它们发送一个终止消息。

撰写回答