我想知道在30分钟后收到执行任务的请求后使用threading.Timer
是否有意义。我们来看看代码:
def late_process():
if not finish:
Timer(1800, late_process, ()).start()
# Work to do ...
# Write logs, send emails... whatever
@app.route('/timely-req')
def timely():
finish = False
Timer(1800, late_process, ()).start()
return 'To be executed in 30 minutes'
@app.route('/end-timely-req')
def end():
finish = True
return 'Process stopped'
因此,主要思想是触发此过程的执行(每30分钟一次)。这是有效的,但我不知道threading.Timer
是否是一个好主意,因为请求将返回,但我将离开服务器,每隔30分钟唤醒驻留线程。这只是一个或者是一个可以使用和试用的原型,并且不最终解决方案将在某一天看到生产。
我使用的是Python 2.6,Flask 0.10.1和Uwsgi。
答案 0 :(得分:6)
threading.Timer
有一个笨重的界面(例如,它不会每30分钟自动重复一次,你必须自己再次关闭它 - 而明显的方法就是这样做意味着它逐渐向后漂移随着时间的推移)。
这也是一个重量级的实施;如果你有很多计时器,你有很多线程,这可能是你的操作系统的线程调度程序的问题。
如果你只有一个计时器,并且你每30分钟只运行一次,那就是日志翻转和摘要电子邮件之类的“背景”内容,这些问题都可能是可以接受的 - 事实上,几乎没有问题。但是你确实遇到了问题。
首先,在函数内部执行finish = True
会创建一个名为finish
的局部变量,覆盖同名的任何全局变量。如果要修改全局变量,则需要在函数顶部添加global finish
语句。*
此外,每当有人点击/timely-req
时,您就会启动新的Timer
。这意味着我可以轻松地对您的服务器进行操作 - 甚至可能只是偶然地请求该URL几百次。所以,如果你要这样做,你可能想让它成为一个单例对象,如果它还不存在则创建它。在PyPI,ActiveState和Flask贡献上还有许多单线程多计时器调度程序的实现,它们会自动解决这个问题(以及你可能不关心的上述两个问题)。 / p>
此外,当您告诉Flask退出时,它会停止接收请求,让您的代码完成现有请求,让您创建的任何线程完成,然后退出。如果你有一个永远不会完成的线程,这不会起作用,所以如果你想要优雅的关闭功能,你必须自己添加它(设置finish = True
)。
最后,在没有锁或其他线程同步设备的情况下在线程之间共享全局变量不是线程安全的。特别是,从理论上讲,您的服务器线程可能会设置finish = True
,并且您的计时器线程将永远看到缓存中的旧值。在实践中,至少在CPython中,你会逃避这一点。 ** 但是最好正确编写代码。
*此外,Flask有一个可选功能,可以使用线程局部对象伪造全局变量 - 也就是说,每个线程都会获得自己的finish
副本。并且,由于每个Timer
实例都是新线程,因此设置finish = True
不会影响它。因此,如果您使用此功能,则无法以这种方式使用Timer
。但是如果你不了解它,你可能没有使用它。
**在CPython中,重新绑定一个全局变量是一个原子操作,但不是互锁的 - 但由于GIL,它实际上总是可以被其下一个GIL时间片用于每个其他线程 - 这是一个几分之一秒,所以你的30分钟延迟甚至都不会注意到。在最糟糕的情况下,它会比你想要的还多一次 - 如果网页上的点击比预期的要长几毫秒,那么这种情况可能已经发生了。