在请求我的Flask Python应用程序后使用threading.Timer

在请求我的Flask Python应用程序后使用threading.Timer,python,flask,Python,Flask,我想知道在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():

我想知道在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分钟一次)。这是工作,但我不知道如果
线程。Timer
是一个好主意,因为请求将返回,但我离开服务器时,每30分钟唤醒一个驻留线程。这只是一个要使用和尝试的原型,它不是最终的解决方案,总有一天会投入生产


我正在使用Python 2.6、Flask 0.10.1和Uwsgi。

线程。计时器的界面有点笨重(例如,它不会每30分钟自动重复一次,你必须自己重新启动它,而显而易见的方式意味着它会随着时间的推移逐渐落后)

这也是一个有点重量级的实现;如果你有很多定时器,你就有很多线程,这对你的操作系统的线程调度程序来说可能是个问题

如果你只有一个计时器,而且你每30分钟只运行一次,而这些都是“后台”的东西,比如日志滚动和摘要邮件,那么这些问题实际上都是可以接受的,甚至连问题都没有。但你确实有一些问题

首先,在函数中执行
finish=True
会创建一个名为
finish
的局部变量,覆盖任何同名的全局变量。如果要修改全局变量,需要在函数顶部使用
global finish
语句*

此外,每次有人点击
/timetime req
,您都会启动一个新的
计时器。这意味着我可以很容易地关闭你的服务器,甚至可能是偶然的,只要请求该URL几百次。因此,如果要这样做,可能需要将其设置为单例对象,并在它不存在时创建计时器。在PyPI、ActiveState和Flask上也有许多单线程多定时器调度器的实现,它们会自动解决这个问题(以及上面两个您可能不关心的问题)

另外,当您告诉Flask退出时,它停止接受请求,让您的代码完成现有请求,让您创建的所有线程完成,然后退出。如果你有一个永远不会结束的线程,这是行不通的,所以如果你想要优雅的关机功能,你必须自己添加它(设置
finish=True

最后,在没有锁或其他线程同步设备的线程之间共享全局变量是不安全的。特别是,从理论上讲,您的服务器线程可能会设置
finish=True
,而您的计时器线程将永远看到缓存中的旧值。在实践中,至少使用CPython,您可以避免这种情况。**但最好正确编写代码

*此外,Flask还有一个可选功能,即使用线程本地对象伪造全局线程,即每个线程都将获得自己的
finish
副本。而且,由于每个
Timer
实例都是一个新线程,因此设置
finish=True
不会影响它。因此,如果您正在使用此功能,则不能以这种方式使用
Timer
。但是如果你不知道的话,你可能没有使用它


**在CPython中,重新绑定一个全局变量是一个原子操作,但不是互锁的,但由于GIL,它实际上总是通过下一个GIL时间片(只有几分之一秒)对每个其他线程可用,因此您的30分钟延迟甚至不会注意到。在最坏的情况下,它将比您希望的运行时间多运行一次,如果单击网页的时间比您预期的长几毫秒,那么这种情况可能已经发生。

线程。计时器的界面有点笨重(例如,它不会每30分钟自动重复一次,你必须自己重新启动它,这样做的明显方式意味着它会随着时间的推移逐渐落后)

这也是一个有点重量级的实现;如果你有很多定时器,你就有很多线程,这对你的操作系统的线程调度程序来说可能是个问题

如果你只有一个计时器,而且你每30分钟只运行一次,而且都是“后台”的东西,比如日志滚动和摘要邮件,那么这些问题实际上都是可以接受的,几乎没有问题。但是你确实有问题

首先,在函数中执行
finish=True
会创建一个名为
finish
的局部变量,覆盖任何同名的全局变量。如果要修改全局变量,需要在函数顶部使用
global finish
语句*

另外,每次有人点击
/timetime req
,你都会启动一个新的
计时器
。这意味着我可以很容易地关闭你的服务器,甚至可能只是偶然地请求了几百次该URL。因此,如果你要这样做,你可能想将其作为一个单例对象,并在它不存在时创建计时器在PyPI、ActiveState和Flask上也有许多单线程多定时器调度器的实现,它们会自动解决这个问题(以及上面两个您可能不关心的问题)

此外,当您告诉Flask退出时,它会停止接收请求,让您的代码完成现有请求,让您创建的所有线程完成,然后退出。如果您有一个线程从未完成,这是行不通的,因此如果您想要优雅的关机功能,您必须自己添加它(设置
finish=True

最后,在没有锁或其他线程同步设备的线程之间共享全局变量是不安全的