关于Windows:在python中运行超时代码的正确方法

right way to run some code with timeout in Python

我在网上找到了一些关于超时运行代码的讨论和活动模式。看起来有一些常见的方法:

  • 使用运行代码的线程,并使用带有超时的join。如果超时时间已过-终止线程。这在python(使用的私有_Thread__stop函数)中不直接支持,因此这是不好的做法。
  • 使用signal.SIGALRM—但这种方法不适用于Windows!
  • 使用带有超时的子进程-但这太重了-如果我想经常启动可中断的任务,我不希望为每个任务都触发进程怎么办!

那么,正确的方法是什么?我不是在问解决方法(例如使用Twisted和AsyncIO),而是实际解决实际问题的方法——我有一些函数,我只想在超时的情况下运行它。如果超时时间已过,我希望控制权恢复。我希望它能在Linux和Windows上工作。


一个完全通用的解决方案,真的,真的不存在。对于给定的域,必须使用正确的解决方案。

  • 如果你想让你完全控制的代码超时,你必须写它来合作。这种代码必须能够以某种方式分解成小块,比如在事件驱动系统中。如果您可以确保没有什么东西能将锁保持太长时间,那么您也可以通过线程来实现这一点,但正确处理锁实际上相当困难。

  • 如果您因为担心代码失控而需要超时(例如,如果您担心用户会要求计算器计算9**(9**9)),则需要在另一个进程中运行它。这是唯一能够充分隔离它的简单方法。在事件系统或其他线程中运行它是不够的。也可以将事情分成与其他解决方案类似的小块,但需要非常小心的处理,通常不值得这样做;无论如何,这不允许您执行与运行Python代码相同的精确操作。


您可能正在寻找的是多处理模块。如果subprocess太重,那么这也可能不适合您的需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
import multiprocessing

def do_this_other_thing_that_may_take_too_long(duration):
    time.sleep(duration)
    return 'done after sleeping {0} seconds.'.format(duration)

pool = multiprocessing.Pool(1)
print 'starting....'
res = pool.apply_async(do_this_other_thing_that_may_take_too_long, [8])
for timeout in range(1, 10):
    try:
        print '{0}: {1}'.format(duration, res.get(timeout))
    except multiprocessing.TimeoutError:
        print '{0}: timed out'.format(duration)

print 'end'


我在Eventlet库中找到了这个:

http://eventlet.net/doc/modules/timeout.html

1
2
3
4
5
6
7
from eventlet.timeout import Timeout

timeout = Timeout(seconds, exception)
try:
    ... # execution here is limited by timeout
finally:
    timeout.cancel()


如果与网络相关,您可以尝试:

1
2
import socket
socket.setdefaulttimeout(number)

另一种方法是使用FaultHandler:

1
2
3
4
5
6
7
8
9
10
11
12
import time
import faulthandler


faulthandler.enable()


try:
    faulthandler.dump_tracebacks_later(3)
    time.sleep(10)
finally:
    faulthandler.cancel_dump_tracebacks_later()

注意:FaultHandler模块是python3.3中stdlib的一部分。


对于不在C扩展或I/O等待中延迟延长时间的"普通"python代码,可以通过使用sys.settrace()设置跟踪函数来实现目标,该函数在超时时中止正在运行的代码。

这是否足够取决于您运行的代码是如何协作还是恶意的。如果它的行为良好,跟踪函数就足够了。


用"with"构造求解并合并来自-

  • 超时函数(如果完成时间过长)
  • 这条线更管用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    import threading, time

    class Exception_TIMEOUT(Exception):
        pass

    class linwintimeout:

        def __init__(self, f, seconds=1.0, error_message='Timeout'):
            self.seconds = seconds
            self.thread = threading.Thread(target=f)
            self.thread.daemon = True
            self.error_message = error_message

        def handle_timeout(self):
            raise Exception_TIMEOUT(self.error_message)

        def __enter__(self):
            try:
                self.thread.start()
                self.thread.join(self.seconds)
            except Exception, te:
                raise te

        def __exit__(self, type, value, traceback):
            if self.thread.is_alive():
                return self.handle_timeout()

    def function():
        while True:
            print"keep printing ...", time.sleep(1)

    try:
        with linwintimeout(function, seconds=5.0, error_message='exceeded timeout of %s seconds' % 5.0):
            pass
    except Exception_TIMEOUT, e:
        print"  attention !! execeeded timeout, giving up ... %s" % e

我就是这样解决的:因为我工作得很好(在窗户里,一点也不重),我希望它对别人有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import threading
import time

class LongFunctionInside(object):
    lock_state = threading.Lock()
    working = False

    def long_function(self, timeout):

        self.working = True

        timeout_work = threading.Thread(name="thread_name", target=self.work_time, args=(timeout,))
        timeout_work.setDaemon(True)
        timeout_work.start()

        while True:  # endless/long work
            time.sleep(0.1)  # in this rate the CPU is almost not used
            if not self.working:  # if state is working == true still working
                break
        self.set_state(True)

    def work_time(self, sleep_time):  # thread function that just sleeping specified time,
    # in wake up it asking if function still working if it does set the secured variable work to false
        time.sleep(sleep_time)
        if self.working:
            self.set_state(False)

    def set_state(self, state):  # secured state change
        while True:
            self.lock_state.acquire()
            try:
                self.working = state
                break
            finally:
                self.lock_state.release()

lw = LongFunctionInside()
lw.long_function(10)

主要的想法是创建一个线程,该线程将与"long work"并行睡眠,并在唤醒(超时后)时更改安全变量状态,long函数在工作期间检查安全变量。我对python编程很陌生,所以如果这个解决方案有一个基本错误,比如资源、时间、死锁问题,请回答)。


如果您运行的代码希望在一个设定的时间之后死亡,那么您应该正确地编写它,这样无论它是线程还是子进程,都不会对关闭产生任何负面影响。这里有一个带撤销的命令模式是很有用的。

所以,这真的取决于你杀死它时线程在做什么。如果只是计算数字谁在乎你杀了它。如果它与文件系统交互,而您杀死了它,那么您可能真的应该重新考虑您的策略。

关于线程,python支持什么?守护进程线程和联接。如果您加入了一个仍处于活动状态的守护进程,为什么Python会让主线程退出?因为它知道使用守护进程线程的人(希望)会以线程死亡时不重要的方式编写代码。在这个上下文中,给一个连接超时,然后让主节点死掉,这样就可以使用任何守护进程线程。