Python 线程以精确的时间间隔执行

Python thread executing at precise time interval

我想制作一个在指定时间间隔执行的函数,假设每 30 秒执行一次,时间是这样的:12:37:00.00000012:37:30.00000012:38:00.000000

现在简单的方法是创建一个线程 运行 一个函数,该函数有一个 while 循环,最后有一个 sleep(30),因此在最后等待 30 秒。我的应用程序将是 运行 24/7,并且在时间上,比方说从开始算起 2-3 小时,它将执行该功能的实际时间将是 X:X:00.5X:X:33.5(因为上面有很多代码 sleep(30)),因此我的工作每 30 秒就会有一点延迟,而在几个小时内,会有相当大的延迟。

我正在寻找的是一种优雅的方法,可以设置为每 30 秒触发一次的闹钟,但始终正好是 30 秒,不多也不少,因为精度是必须的。

Python 可能不是完成这项工作的最佳工具,但如果您解耦由 sleep 等函数测量的时间,并在每个步骤中使用 system.time 重新校准,从长远来看,您可能可以获得足够准确的东西来满足您的需求:

这使用 time.sleep 来等待大部分时间间隔。在等待时间快结束时,它会根据系统时间更主动地检查经过的时间。

它跟踪已经过去的间隔数,并使用经过的总时间来 minimize/correct 当您在多个间隔上堆叠间隔错误时发生的错误累积。

可能会发生一个事件偶尔会延迟触发(由于外部系统条件、负载等...),但后续事件将重新校准以按时触发。

import time

interval = 30
steps = 0

print("start period      periods        Error", flush=True)
print(time.time(), flush=True)

start = time.time()
while True:
    steps += 1

    time.sleep(interval - 0.1)

    while time.time() < (start + interval * steps):
        pass

    #execute your stuff
    print(time.time(), interval * steps, time.time() - (start + (interval * steps)), flush=True)  # prints the actual interval

输出:

start period      periods        Error
1511782735.662815
1511782765.664593 30 1.9073486328125e-06
1511782795.6645918 60 7.152557373046875e-07
1511782825.664594 90 3.814697265625e-06
1511782855.664593 120 1.9073486328125e-06
1511782885.664594 150 2.86102294921875e-06
1511782915.664593 180 1.9073486328125e-06
1511782945.664593 210 1.9073486328125e-06
1511782975.664594 240 2.86102294921875e-06
1511783005.664593 270 1.9073486328125e-06

误差代表的是从一开始的漂移,并不是每个区间都累加的。

您可以推出自己的方法:

from datetime import datetime, timedelta
import time

def every_30s():
    print "fired at", datetime.now()


now = datetime.now()

# Align to the next 30 second event from the current time
if now.second >= 30:
    next_fire = now.replace(second=30, microsecond=0) + timedelta(seconds=30)
else:
    next_fire = now.replace(second=0, microsecond=0) + timedelta(seconds=30)

sleep = (next_fire - now).seconds - 2

while True:
    # Sleep for most of the time
    time.sleep(sleep)

    # Wait until the precise time is reached
    while datetime.now() < next_fire:
        pass

    every_30s()
    next_fire += timedelta(seconds=30)   # Advance 30 seconds
    sleep = 28

这首先与下一个 30 秒间隔对齐并触发事件。然后它会休眠 28 秒(以减少负载),然后快速检查下一次触发时间是否已过。下一次开火时间提前30秒。

这将确保时间永远不会漂移。如果机器负载很重,它可能会稍晚触发,但下一个事件会再次准时。

示例输出:

fired at 2017-11-27 11:41:00
fired at 2017-11-27 11:41:30
fired at 2017-11-27 11:42:00
fired at 2017-11-27 11:42:30
fired at 2017-11-27 11:43:00
fired at 2017-11-27 11:43:30
fired at 2017-11-27 11:44:00