1

I am looking for a way to run a method every second, regardless of how long it takes to run. In looking for help with that, I ran across

Run certain code every n seconds

and in trying it, found that it doesn't work correctly. It appears to have the very problem I'm trying to avoid: drift. I tried adding a "sleep(0.5)" after the print, and it does in fact slow down the loop, and the interval stays at the 1.003 (roughly) seconds.

Is there a way to fix this, to do what I want?

(venv) 20170728-153445 mpeck@bilbo:~/dev/whiskerlabs/aphid/loadtest$ cat a.py
import threading
import time
def woof():
  threading.Timer(1.0, woof).start()
  print "Hello at %s" % time.time()
woof()
(venv) 20170728-153449 mpeck@bilbo:~/dev/whiskerlabs/aphid/loadtest$ python a.py
Hello at 1501281291.84
Hello at 1501281292.85
Hello at 1501281293.85
Hello at 1501281294.85
Hello at 1501281295.86
Hello at 1501281296.86
Hello at 1501281297.86
Hello at 1501281298.87
Hello at 1501281299.87
Hello at 1501281300.88
Hello at 1501281301.88
Hello at 1501281302.89
Hello at 1501281303.89
Hello at 1501281304.89
Hello at 1501281305.89
Hello at 1501281306.9
Hello at 1501281307.9
Hello at 1501281308.9
Hello at 1501281309.91
Hello at 1501281310.91
Hello at 1501281311.91
Hello at 1501281312.91
Hello at 1501281313.92
Hello at 1501281314.92
Hello at 1501281315.92
Hello at 1501281316.93
  • I wonder if the extra time is the time to create a new thread and if a https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor thread pool executor would help – Ryan Stout Jul 29 '17 at 00:37
  • I don't believe so, at least not without some work. Underneath the hood, the OS can take longer than requested to get to you--it doesn't make any hard guarantees. For instance, things like `time.sleep()` only guarantee that you'll sleep at least as long as you ask--not exactly as long. In the past, I have done things like keep track of the overhead and wait for the desired time minus that overhead. The problem is that a small hiccup can disrupt things (say a process hogging the cpu too much), so it requires some careful thought to get right. – John Szakmeister Jul 29 '17 at 01:02
  • yeah, I wrote some code to sleep for .995 seconds and it still sleeps for about 1.004 seconds each loop iteration: import time after = time.time() while True: old = after new = old + 1 now = time.time() print ("should sleep for",new - now) time.sleep(new - now) after =time.time() print ("slept for" , after-now) should sleep for 0.9999990463256836 slept for 1.0031487941741943 should sleep for 0.9999239444732666 slept for 1.0022780895233154 should sleep for 0.9999380111694336 slept for 1.000175952911377 – Ryan Stout Jul 29 '17 at 01:05

2 Answers2

2
  1. Don't use a threading.Timer if you don't actually need a new thread each time; to run a function periodically sleep in a loop will do (possibly in a single separate thread).

  2. Whatever method you use to schedule the next execution, don't wait for the exact amount of time you use as interval - execution of the other statements take time, so the result is drift as you can see. Instead, write down the initial time in a variable, and at each iteration calculate the next time when you want to schedule execution and sleep for the difference between now and then.

    interval = 1.
    next_t = time.time()
    while True:
        next_t += interval
        time.sleep(next_t - time.time())
        # do whatever you want to do 
    

    (of course you may refine it for better overall accuracy, but this at least should avoid drift)

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • This is exactly the kind of solution I was thinking about in my comment above--something that dynamically adapts to the overhead. You just have to be careful when things run amuck, like `sleep()` taking much longer than expected due to other activity in the system. +1 – John Szakmeister Jul 29 '17 at 01:17
0

I'm pretty sure the problem with that code is that it takes Python some time (apparently around .3s) to execute the call to your function woof, instantiate a new threading.Timer object, and print the current time. So basically, after your first call to the function, and the creation of a threading.Timer, Python waits exactly 1s, then calls the function woof (a decisecond or so), creates a new Timer object (yet another decisecond at least), and finally prints the current time with some delay.

The solution to actually run a program every second seems to be the Twisted library, as said on this other post, but I didn't really try it myself...

Edit: I would mark the question as possible duplicate but I apparently don't have enough reputation to do that yet... If someone can be kind enough to do so with at least the link I provided, it would be cool :)

Arthur Spoon
  • 442
  • 5
  • 18