在python中,对于玩具示例:
for x in range(0, 3):
# call function A(x)
我想继续for循环,如果函数A跳过它需要超过5秒,这样我就不会卡住或浪费时间。
通过做一些搜索,我意识到子进程或线程可能会有所帮助,但我不知道如何在这里实现。 任何帮助都会很棒。 感谢
答案 0 :(得分:29)
我认为创建一个新流程可能过度。如果您使用的是Mac或基于Unix的系统,则应该能够使用signal.SIGALRM强制超时耗时的功能。这将适用于网络闲置的功能或您通过修改功能无法处理的其他问题。我有一个在这个答案中使用它的例子:
https://stackoverflow.com/a/24921763/3803152
在这里编辑我的答案,虽然我不确定我是否应该这样做:
import signal
class TimeoutException(Exception): # Custom exception class
pass
def timeout_handler(signum, frame): # Custom signal handler
raise TimeoutException
# Change the behavior of SIGALRM
signal.signal(signal.SIGALRM, timeout_handler)
for i in range(3):
# Start the timer. Once 5 seconds are over, a SIGALRM signal is sent.
signal.alarm(5)
# This try/except loop ensures that
# you'll catch TimeoutException when it's sent.
try:
A(i) # Whatever your function that might hang
except TimeoutException:
continue # continue the for loop if function A takes more than 5 second
else:
# Reset the alarm
signal.alarm(0)
这基本上设置了一个5秒的计时器,然后尝试执行你的代码。如果在时间用完之前无法完成,则会发送一个SIGALRM,我们将其捕获并转换为TimeoutException。这会迫使你进入except块,你的程序可以在那里继续。
编辑:哎呀,TimeoutException
是一个类,而不是一个函数。谢谢,abarnert!
答案 1 :(得分:7)
如果你可以打破你的工作并经常检查,这几乎总是最好的解决方案。但有时候这是不可能的 - 例如,也许你正在从缓慢的文件共享中读取文件,每隔一段时间就会挂起30秒。要在内部处理这个问题,您必须围绕异步I / O循环重构整个程序。
如果您不需要跨平台,您可以使用* nix(包括Mac和Linux)上的信号,Windows上的APC等。但如果您需要跨平台,那不会&# 39;工作。
所以,如果你真的需要同时做,你可以,有时你必须这样做。在这种情况下,您可能希望为此使用一个进程,而不是一个线程。你不能安全地杀死一个线程,但你可以杀死一个进程,它可以像你想要的那样安全。此外,如果线程花了5秒以上,因为它受CPU限制,你不想通过GIL与它战斗。
这里有两个基本选项。
首先,您可以将代码放在另一个脚本中并使用subprocess
运行它:
subprocess.check_call([sys.executable, 'other_script.py', arg, other_arg],
timeout=5)
由于这是通过正常的子进程通道,你可以使用的唯一通信是一些argv
字符串,一个成功/失败返回值(实际上是一个小整数,但那不是更好),以及可选的一大块文本和一大块文本。
或者,您可以使用multiprocessing
生成类似线程的子进程:
p = multiprocessing.Process(func, args)
p.start()
p.join(5)
if p.is_alive():
p.terminate()
正如您所看到的,这有点复杂,但在某些方面它会更好:
任何类型的并行性的一个大问题是共享可变数据 - 例如,将后台任务更新为全局字典作为其工作的一部分(您的评论称您正在尝试这样做)。使用线程,您可以放弃它,但竞争条件可能导致数据损坏,因此您必须非常小心锁定。通过子进程,您根本无法逃脱它。 (是的,您可以使用共享内存,如Sharing state between processes所述,但这仅限于简单类型,如数字,固定数组和您知道如何定义为C结构的类型,它只会让您回到同一个作为线程的问题。)
理想情况下,您排列的内容使得您不需要在流程运行时共享任何数据 - 您将dict
作为参数传递并返回dict
作为结果。当你想要在后台放置一个先前同步的功能时,这通常很容易安排。
但是,如果,部分结果优于没有结果呢?在这种情况下,最简单的解决方案是通过队列传递结果。您可以使用显式队列执行此操作,如Exchanging objects between processes中所述,但这样做更容易。
如果您可以将整体流程分解为单独的任务,每个值(或一组值)中的一个您希望坚持在词典中,您可以在Pool
上安排它们 - 或者更好的是, concurrent.futures.Executor
。 (如果您使用的是Python 2.x或3.1,请参阅PyPI上的backport futures
。)
让我们说你的慢功能看起来像这样:
def spam():
global d
for meat in get_all_meats():
count = get_meat_count(meat)
d.setdefault(meat, 0) += count
相反,你要这样做:
def spam_one(meat):
count = get_meat_count(meat)
return meat, count
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
results = executor.map(spam_one, get_canned_meats(), timeout=5)
for (meat, count) in results:
d.setdefault(meat, 0) += count
你在5秒内获得的结果会被添加到dict中;如果不是全部,那么其余部分都会被放弃,并且会引发TimeoutError
(你可以随意处理它 - 记录它,做一些快速回退代码,无论如何)。
如果任务真的是独立的(因为它们在我的愚蠢的小例子中,但当然它们可能不在您的真实代码中,至少没有重大的重新设计),您可以免费并行化工作删除max_workers=1
。然后,如果你在一台8核机器上运行它,它将启动8名工作人员并给他们每个工作的1/8,并且事情将更快地完成。 (通常不是快8倍,但通常快3-6倍,这仍然相当不错。)
答案 2 :(得分:6)
评论是正确的,你应该在里面检查。这是一个潜在的解决方案。请注意,异步函数(例如,通过使用线程)与此解决方案不同。这是同步的,这意味着它仍然会串行运行。
import time
for x in range(0,3):
someFunction()
def someFunction():
start = time.time()
while (time.time() - start < 5):
# do your normal function
return;
答案 3 :(得分:1)
这似乎是更好的主意(对不起,还不确定事物的python名称):
import signal
def signal_handler(signum, frame):
raise Exception("Timeout!")
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(3) # Three seconds
try:
for x in range(0, 3):
# call function A(x)
except Exception, msg:
print "Timeout!"
signal.alarm(0) # reset
答案 4 :(得分:1)
基于TheSoundDefense答案,也许有人觉得这个装饰器很有用:
import time
import signal
class TimeoutException(Exception): # Custom exception class
pass
def break_after(seconds=2):
def timeout_handler(signum, frame): # Custom signal handler
raise TimeoutException
def function(function):
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
res = function(*args, **kwargs)
signal.alarm(0) # Clear alarm
return res
except TimeoutException:
print u'Oops, timeout: %s sec reached.' % seconds, function.__name__, args, kwargs
return
return wrapper
return function
测试:
@break_after(3)
def test(a,b,c):
return time.sleep(10)
>>> test(1,2,3)
Oops, timeout: 3 sec reached. test (1, 2, 3) {}