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