Python:如何管理和杀死卡住或等待超时的工作线程。。。?

Python:如何管理和杀死卡住或等待超时的工作线程。。。?,python,multithreading,Python,Multithreading,这已经讨论了很多次,但我仍然没有很好地掌握如何最好地完成这一点 假设我有两个线程:一个主应用程序线程和一个工作线程。主应用程序线程(假设它是一个WXWidgets GUI线程,或者是一个在控制台循环并接受用户输入的线程)可能有理由停止工作线程——用户关闭应用程序时,单击了停止按钮,主线程中发生了一些错误,等等 通常建议设置一个线程经常检查的标志,以确定是否退出。然而,我对建议的方法有两个问题: 首先,在代码中不断地检查标志会使代码非常难看,而且由于大量的代码重复,很容易出现问题。举个例子: de

这已经讨论了很多次,但我仍然没有很好地掌握如何最好地完成这一点

假设我有两个线程:一个主应用程序线程和一个工作线程。主应用程序线程(假设它是一个WXWidgets GUI线程,或者是一个在控制台循环并接受用户输入的线程)可能有理由停止工作线程——用户关闭应用程序时,单击了停止按钮,主线程中发生了一些错误,等等

通常建议设置一个线程经常检查的标志,以确定是否退出。然而,我对建议的方法有两个问题:

首先,在代码中不断地检查标志会使代码非常难看,而且由于大量的代码重复,很容易出现问题。举个例子:

def WorkerThread():

    while (True):
        doOp1() # assume this takes say 100ms.
        if (exitThread == True): 
            safelyEnd()
            return
        doOp2() # this one also takes some time, say 200ms
        if (exitThread == True): 
            safelyEnd()
            return
        if (somethingIsTrue == True):
            doSomethingImportant()
            if (exitThread == True): return
            doSomethingElse()
            if (exitThread == True): return 
        doOp3() # this blocks for an indeterminate amount of time - say, it's waiting on a network respond
        if (exitThread == True): 
            safelyEnd()
            return
        doOp4() # this is doing some math
        if (exitThread == True): 
            safelyEnd()
            return
        doOp5() # This calls a buggy library that might block forever.  We need a way to detect this and kill this thread if it's stuck for long enough...
        saveSomethingToDisk() # might block while the disk spins up, or while a network share is accessed...whatever
        if (exitThread == True): 
            safelyEnd()
            return


def safelyEnd():
    cleanupAnyUnfinishedBusiness() # do whatever is needed to get things to a workable state even if something was interrupted
    writeWhatWeHaveToDisk() # it's OK to wait for this since it's so important
如果我添加了更多代码或更改了代码,我必须确保我添加了所有的检查块。如果我的工作线程是一个非常长的线程,我可以轻松地进行几十次甚至数百次这样的检查。非常麻烦

想想其他的问题。如果doOp4()确实意外死锁,我的应用程序将永远旋转,永远不会退出。不是一个好的用户体验

使用守护进程线程也不是一个好的选择,因为它剥夺了我执行
safelyEnd()
代码的机会。这段代码可能很重要-刷新磁盘缓冲区,为调试目的写入日志数据,等等

其次,我的代码可能会调用那些我没有机会经常检查的函数。假设此函数存在,但其代码我无权访问-例如库的一部分:

def doOp4():
    time.sleep(60) # imagine that this is a network thread, that waits for 60 seconds for a reply before returning.
如果超时时间是60秒,即使我的主线程发出了结束线程的信号,它仍然可能会在那里停留60秒,这时它完全可以停止等待网络响应并退出。但是,如果该代码是我没有编写的库的一部分,我无法控制它的工作方式

即使我确实为网络检查编写了代码,我基本上也必须对其进行重构,使其在检查退出线程之前循环60次并等待1秒,而不是等待60秒!又一次,非常混乱

所有这些的结果是,感觉能够轻松实现这一点的好方法是在特定线程上以某种方式导致异常。如果我能做到这一点,我可以将整个工作线程的代码包装在一个try块中,并将
safelyEnd()
代码放入异常处理程序,甚至是
finally

有没有一种方法可以实现这一点,或者用另一种技术重构代码,从而使事情顺利进行?理想情况下,当用户请求退出时,我们希望让他们等待尽可能少的时间。似乎必须有一个简单的方法来实现这一点,因为这在应用程序中非常常见

大多数线程通信对象不允许这种类型的设置。它们可能允许使用一种更干净的方法来拥有一个退出标志,但它仍然不能消除不断检查该退出标志的需要,而且它仍然不会处理由于外部调用或因为它只是处于繁忙循环中而导致的线程阻塞

对我来说最重要的是,如果我有一个很长的worker线程过程,我就必须在它上乱扔数百个标志检查。这看起来太混乱了,感觉这不是一个很好的编码实践。一定有更好的方法

如果您有任何建议,我们将不胜感激。

首先,您可以通过使用异常来减少此操作的冗长性和重复性,而不需要从外部将异常或任何其他新技巧或语言功能引入线程:

def WorkerThread():
    class ExitThreadError(Exception):
        pass
    def CheckEnd():
        if exitThread:
            raise ExitThreadError()

    try:
        while True:
            doOp1() # assume this takes say 100ms.
            CheckEnd()
            doOp2() # this one also takes some time, say 200ms
            CheckEnd()
            # etc.
    except ExitThreadError:
        safelyEnd()
请注意,您确实应该使用
条件来保护
exitThread
——这是结束检查的另一个很好的理由,因此您只需要在一个地方修复它

无论如何,我去掉了一些多余的括号,
==True
检查,等等,这些检查没有给代码添加任何内容;希望你仍能看到它与原作的相同之处


通过将函数重组为一个简单的状态机,您可以更进一步地实现这一点;那么你甚至不需要一个例外。我将展示一个可笑的小例子,其中每个状态总是隐式地转换到下一个状态,不管发生什么。对于这种情况,重构显然是合理的;对于您的真实代码,它是否合理,只有您才能真正知道

def WorkerThread():
    states = (doOp1, doOp2, doOp3, doOp4, doOp5)
    current = 0
    while not exitThread:
        states[current]()
        current += 1
    safelyEnd()

这两种方法都没有帮助你在一步中间中断。

如果你有一个需要60秒的函数,而你对此却无能为力,那么在这60秒内,你就没有办法取消你的线程,你对此也无能为力。事情就是这样

但通常情况下,需要60秒的操作实际上是在
select
上执行阻塞操作,您可以创建一个管道,将其读取端粘贴在
select
中,然后在另一端写入以唤醒线程

或者,在您感觉不舒服的情况下,通常只是关闭/删除/等。函数正在等待/处理/以其他方式使用的文件或其他对象通常会保证它在异常情况下快速失败。当然,有时它保证了一个segfault,或者数据损坏,或者有50%的几率退出,50%的几率永远挂起,或者……所以,即使你不能控制
doOp4
函数,你最好能够分析它的源代码和/或白盒测试它

如果出现最坏的情况,那么是的,您必须将一个60秒超时更改为60个1秒超时。但通常不会这样


最后,如果你真的需要杀死一只泰尔
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()