为什么Python的线程对象有"start"而没有"stop"?

4
Python模块threading有一个对象Thread,可用于在不同的线程中运行进程和函数。该对象具有start方法,但没有stop方法。为什么不能通过调用简单的stop方法来停止Thread?我可以想象出某些情况下使用join方法是不方便的...
5个回答

10

start 可以是通用的,因为它只是触发线程的目标,但是一个通用的 stop 会做什么呢?根据你的线程正在做什么,你可能需要关闭网络连接、释放系统资源、转储文件和其他流,或者执行任何数量的其他自定义、非平凡的任务。任何可以以通用方式完成这些事情的系统都会给每个线程增加太多开销,不值得,并且会变得非常复杂,并充满特殊情况,几乎不可能使用。您可以在主线程中跟踪所有已创建的线程而不 join它们,然后在主线程关闭时检查它们的运行状态并向它们传递某种终止消息。


3
追问:通常情况下,按下CTRL-C可以停止Python程序。那么在这种情况下与线程的区别是什么?为什么不禁止使用CTRL-C停止程序,尽管所有论点仍然成立?回答:在Python程序中,按下CTRL-C会导致主线程退出程序。但对于多线程程序而言,如果在子线程中按下CTRL-C,它不能安全地终止线程并退出程序。这可能会导致资源泄漏和数据损坏等问题。虽然存在这些风险,但没有禁止使用CTRL-C来停止程序,因为它是在大多数情况下正常工作的有效方法。 - Alex
1
这是一个系统事件,默认情况下会导致异常并关闭您的程序。正如@Anony-Mousse在他的答案中所说,您需要捕获该事件并以自定义方式处理它,以使您的线程正确清理。此外,如果您正在使用CTRL-C作为结束程序的标准方式,特别是如果您尚未以自定义方式处理该事件,则只会引发错误。让系统终止进程只有在出现问题或像重新启动计算机这样的操作时才应该发生。 - Silas Ray

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)

5

可靠地终止线程并不容易。想想需要进行哪些清理工作:哪些锁(可能与其他线程共享!)应该自动释放?否则,你很容易陷入死锁!

更好的方法是自己实现适当的关闭方式,然后设置

mythread.shutdown = True
mythread.join()

停止线程。

当然,您的线程应该执行一些操作,例如

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

需要经常检查关闭标志。或者,您可以依赖于特定于操作系统的信号机制来中断线程,捕获中断,然后进行清理。

清理是最重要的部分!


如果您的线程中正在运行asyncore.loop(),则无法执行该操作。 - Alex
这样做有很好的理由。因为你希望线程停止并释放它拥有的任何资源。否则,你很可能早晚会遇到死锁问题。 - Has QUIT--Anony-Mousse
但是如何处理在线程中运行的 asyncore.loop() 呢?如何优雅地停止它?(我猜这是另一个问题...) - Alex
Alex:是的,这可能是一个单独的问题。看起来有人已经为您提出了这个问题 :) https://dev59.com/uGPVa4cB1Zd3GeqP65F-。尝试设置一个标志以指示循环应该关闭,并触发一个处理程序来检查它。这似乎是解决您问题的常见方法。我经常有一个单独的线程被阻塞在某个队列上,所以要关闭它们,我会将一个特殊值放入队列中或在每次获取后检查标志。 - monk
哈,我在问这个主要问题之前就找到了同样的问题。我现在正在尝试实现它。无论如何还是谢谢。 - Alex

1
停止线程应该由程序员来实现。例如设计您的线程以检查是否有任何请求立即终止它。如果Python(或任何线程语言)允许您仅停止线程,那么您将拥有停止的代码。这容易出错等。
想象一下,如果您的线程在您杀死/停止它时正在向文件输出,则该文件可能会未完成且损坏。但是,如果您只是向线程发出信号,表示您希望它停止,那么它可以关闭文件,删除文件等。您,程序员,决定如何处理它。Python无法为您猜测。
我建议阅读多线程理论。一个不错的开始:http://en.wikipedia.org/wiki/Multithreading_(software)#Multithreading

0

在某些平台上,您无法强制“停止”线程。这样做也不好,因为线程将无法清理分配的资源。而且当线程正在执行重要任务(如I/O)时,可能会发生这种情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接