所以在Windows上,signal
和thread
approahc通常是坏主意/不适用于函数超时。
我制作了以下超时代码,当代码花费很长时间时,它会从multiprocessing
抛出timeout exception
。这正是我想要的。
def timeout(timeout, func, *arg):
with Pool(processes=1) as pool:
result = pool.apply_async(func, (*arg,))
return result.get(timeout=timeout)
我现在正在尝试将其转换为装饰器样式,以便我可以将其添加到各种函数中,尤其是那些调用外部服务并且我无法控制代码或持续时间的函数。我目前的尝试如下:
class TimeWrapper(object):
def __init__(self, timeout=10):
"""Timing decorator"""
self.timeout = timeout
def __call__(self, f):
def wrapped_f(*args):
with Pool(processes=1) as pool:
result = pool.apply_async(f, (*args,))
return result.get(timeout=self.timeout)
return wrapped_f
它给出了酸洗错误:
@TimeWrapper(7)
def func2(x, y):
time.sleep(5)
return x*y
File "C:UsersrmenkAppDataLocalContinuumanaconda3libmultiprocessingreduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <function func2 at 0x000000770C8E4730>: it's not the same object as __main__.func2
我怀疑这是由于多处理和装饰器玩得不好,但我实际上不知道如何让它们玩得好。关于如何解决这个问题的任何想法?
PS:我已经在这个网站和其他地方做了一些广泛的研究,但没有找到任何有效的答案,无论是鹅卵石,线程,作为功能装饰器还是其他方式。如果你有一个解决方案,你知道适用于Windows和python 3.5,我很乐意使用它。
您要实现的目标在Windows中特别麻烦。核心问题是,当你装饰一个函数时,你会遮蔽它。这恰好在 UNIX 中工作得很好,因为它使用fork
策略来创建新进程。
但是,在Windows中,新进程将是一个空白进程,其中启动了一个全新的Python解释器并加载您的模块。当模块被加载时,装饰器隐藏了真正的函数,使得很难找到pickle
协议。
正确的唯一方法是依靠在装饰期间设置的蹦床功能。你可以看看是如何在pebble
上完成的,但是,只要你不是为了锻炼而做,我建议直接使用pebble
,因为它已经提供了你正在寻找的东西。
from pebble import concurrent
@concurrent.process(timeout=60)
def my_function(var, keyvar=0):
return var + keyvar
future = my_function(1, keyvar=2)
future.result()
您在这里遇到的唯一问题是您在主上下文中测试了修饰的函数。将其移出到其他模块,它可能会起作用。
我写了wrapt_timeout_decorator使用wrapt&dill&multiprocess&pipes与pickle&multiprocessing&queue,因为它可以序列化更多的数据类型。
起初它可能看起来很简单,但在窗口下,可靠的超时装饰器非常棘手 - 你可能会使用我的,它非常成熟和经过测试:
https://github.com/bitranox/wrapt_timeout_decorator
在Windows上,主模块再次导入(但名称为!='main'),因为Python试图在不支持分叉的系统上模拟类似分叉的行为。 多处理尝试通过再次导入具有不同名称的主模块来创建类似于主进程的环境。这就是为什么你需要用著名的" ifname== 'main': ">
import lib_foo
def some_module():
lib_foo.function_foo()
def main():
some_module()
# here the subprocess stops loading, because __name__ is NOT '__main__'
if __name__ = '__main__':
main()
这是Windows操作系统的问题,因为Windows操作系统不支持"fork">
您可以在此处找到更多信息:
在 Python 多处理中使用 __name__=='__main__' 的解决方法
https://docs.python.org/2/library/multiprocessing.html#windows
由于 main.py 再次加载时使用了除">main"以外的名称,因此修饰的函数现在指向不再存在的对象,因此您需要将装饰后的类和函数放入另一个模块中。一般来说(特别是在Windows上),main()程序不应该有任何东西,只有main函数,真正的事情应该发生在模块中。我还习惯于将所有设置或配置放在不同的文件中 - 因此所有进程或线程都可以访问它们(并且将它们放在一个地方,不要忘记在您最喜欢的编辑器中键入提示和名称完成)
"莳萝"序列化程序也能够序列化主上下文,这意味着我们示例中的对象被腌制为"main.lib_foo","main.some_module",">main.main"等。当使用"pickle"时,我们不会有这个限制,缺点是"pickle"不能序列化以下类型:
具有 yields 的函数, 嵌套函数, lambdas, cell, method, unboundmethod, module, code, methodwrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplement, ellipsis, quit
其他莳萝支架:
保存和加载 Python 解释器会话,保存和提取函数和类的源代码,交互式诊断酸洗错误
为了使用装饰器支持更多类型,我们选择了 dill 作为序列化程序,缺点是方法和类不能在主上下文中装饰,而是需要驻留在模块中。
您可以在此处找到更多信息:使用泡菜或莳萝序列化__main__对象