10

我在这里找到了一个创建超时函数的代码,这似乎不起作用。完整的测试代码如下:

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
    import threading
    class InterruptableThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            self.result = None

        def run(self):
            try:
                self.result = func(*args, **kwargs)
            except:
                self.result = default

    it = InterruptableThread()
    it.start()
    it.join(timeout_duration)
    if it.isAlive():
        return default
    else:
        return it.result


def foo():
    while True:
        pass

timeout(foo,timeout_duration=3)

预期行为:代码在 3 秒内结束。问题出在哪里?

4

3 回答 3

13

一个线程不能优雅地杀死另一个线程,因此使用您当前的代码,foo永远不会终止。(thread.daemon = True当只剩下守护线程时,Python 程序将退出,但这不允许您在foo不终止主线程的情况下终止。)

有些人试图使用信号来停止执行,但这在某些情况下可能是不安全的。

如果你可以修改foo,有很多可能的解决方案。例如,您可以检查是否threading.Event退出 while 循环。

但是如果你不能修改foo,你可以使用模块在子进程中运行它,multiprocessing因为与线程不同,子进程可以被终止。这是一个可能看起来如何的示例:

import time
import multiprocessing as mp

def foo(x = 1):
    cnt = 1
    while True:
        time.sleep(1)
        print(x, cnt)
        cnt += 1

def timeout(func, args = (), kwds = {}, timeout = 1, default = None):
    pool = mp.Pool(processes = 1)
    result = pool.apply_async(func, args = args, kwds = kwds)
    try:
        val = result.get(timeout = timeout)
    except mp.TimeoutError:
        pool.terminate()
        return default
    else:
        pool.close()
        pool.join()
        return val


if __name__ == '__main__':
    print(timeout(foo, kwds = {'x': 'Hi'}, timeout = 3, default = 'Bye'))
    print(timeout(foo, args = (2,), timeout = 2, default = 'Sayonara'))

产量

('Hi', 1)
('Hi', 2)
('Hi', 3)
Bye
(2, 1)
(2, 2)
Sayonara

请注意,这也有一些限制。

  • 子进程接收父进程变量的副本。如果您在子流程中修改变量,它不会影响父流程。如果您的函数func需要修改变量,则需要使用共享变量

  • 参数(通过args)和关键字(kwds)必须是可腌制的。

  • 进程比线程更占用资源。通常,您只想在程序开始时创建一次多处理池。每次调用此timeout函数时都会创建一个。Pool这是必要的,因为我们需要pool.terminate()终止foo. 可能有更好的方法,但我还没有想到。
于 2012-12-11T14:53:14.600 回答
2

你需要it变成一个守护线程

it = ...
it.daemon = True
it.start()

否则,它将被创建为用户线程,并且在所有用户线程完成之前,该进程不会停止。

请注意,在您的实现中,线程将继续运行并消耗资源,即使您已超时等待它。CPython 的Global Interpreter Lock可能会进一步加剧这个问题。

于 2012-12-11T13:13:46.273 回答
2

使用的好处multiprocessing是进程不共享内存,并且子进程中发生的任何事情都仅限于该功能,不会导致其他进程终止。为子进程添加 3s 超时的最简单方法是:

import multiprocessing

def my_child():
    function here

process = multiprocessing.Process(target=my_child)
process.daemon = True
process.start()

process.join(3)
if process.is_alive():
    process.terminate()
于 2020-07-06T09:14:55.470 回答