Python thread executing at precise time interval

Viewed 859

I want to make a function that executes at specified time interval, lets say at every 30 seconds, when the time is something like this: 12:37:00.000000, 12:37:30.000000, 12:38:00.000000

Now the easy approach would be to create a thread running a function that has a while loop that has a sleep(30) at the end, thus waiting 30 seconds at the end. My application will be running 24/7, and in time, lets say maybe 2-3 hours from the start, the real time at which it will be executing that function would be X:X:00.5 or X:X:33.5(because there is a lot of code above that sleep(30)), thus delaying my work by a small margin every 30 seconds, and in few hours, by a pretty big margin.

What I'm searching for is a elegant method to set like an alarm that triggers at every 30 seconds, but always exactly 30 seconds, no more, no less, since precision is a must.

2 Answers

You could roll your own method:

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

This first aligns to the next 30 second interval and fires the event. It then sleep for 28 seconds (to reduce load) and then quickly checks if the next fire time has passed. The next fire time is then advanced 30 seconds.

This will ensure that the time never drifts. If there was heavy loading on the machine, it might fire slightly later, but the next event would be on time again.

Sample output:

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

Python may not be the best tool for the job, but if you decouple the time measured by the functions like sleep, and recalibrate at each step with the system.time, you may be able to get something that is accurate enough in the long term for your needs:

This uses time.sleep to wait fro most of the interval. towards the end of the waiting time, it enters a more active checking of the time elapsed against the system time.

It keeps track of the number of intervals that have passed and uses the total time elapsed to minimize/correct the accumulation of errors that happen when you are stacking intervals errors over many intervals.

It may happen that one event occasionally fires late (due to external system conditions, load, etc...), but the following event will be recalibrated to trigger on time.

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

output:

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

The error represents the drift from the beginning, it is not accumulated at each interval.

Related