Search code examples
pythonmultithreadingtimerdelayflags

threadable delay in python 2.7


I'm currently using python (2.7) to write a GUI that has some threads going on. I come across a point that I need to do a roughly about a second delay before getting a piece of information, but I can't afford to have the function takes more than a few millisecond to run. With that in mind, I'm trying to create a Threaded timer that will set a flag timer.doneFlag and have the main function to keep poking to see whether it's done or not.

It is working. But not all the time. The problem that I run into is that sometimes I feel like the time.sleep function in run , doesn't wait fully for a second (sometimes it may not even wait). All I need is that I can have a flag that allow me control the start time and raise the flag when it reaches 1 second.

I maybe doing too much just to get a delay that is threadable, if you can suggest something, or help me find a bug in the following code, I'd be very grateful!

I've attached a portion of the code I used:

from main program:

class dataCollection:
    def __init__(self):
        self.timer=Timer(5)
        self.isTimerStarted=0  
        return

    def StateFunction(self): #Try to finish the function within a few milliseconds

        if self.isTimerStarted==0:
           self.timer=Timer(1.0)
           self.timer.start()
           self.isTimerStarted=1

        if self.timer.doneFlag:
           self.timer.doneFlag=0
           self.isTimerStarted=0
           #and all the other code



import time
import threading
class Timer(threading.Thread):          
    def __init__(self, seconds):            
        self.runTime = seconds          
        self.doneFlag=0
        threading.Thread.__init__(self)
    def run(self):      
        time.sleep(self.runTime)    
        self.doneFlag=1
        print "Buzzzz"

x=dataCollection()
while 1:
    x.StateFunction()
    time.sleep(0.1)

Solution

  • First, you've effectively rebuilt threading.Timer with less flexibility. So I think you're better off using the existing class. (There are some obvious downsides with creating a thread for each timer instance. But if you just want a single one-shot timer, it's fine.)

    More importantly, having your main thread repeatedly poll doneFlag is probably a bad idea. This means you have to call your state function as often as possible, burning CPU for no good reason.

    Presumably the reason you have to return within a few milliseconds is that you're returning to some kind of event loop, presumably for your GUI (but, e.g., a network reactor has the same issue, with the same solutions, so I'll keep things general).

    If so, almost all such event loops have a way to schedule a timed callback within the event loop—Timer in wx, callLater in twisted, etc. So, use that.

    If you're using a framework that doesn't have anything like that, it hopefully at least has some way to send an event/fire a signal/post a message/whatever it's called from outside. (If it's a simple file-descriptor-based reactor, it may not have that, but you can add it yourself just by tossing a pipe into the reactor.) So, change your Timer callback to signal the event loop, instead of writing code that polls the Timer.

    If for some reason you really do need to poll a variable shared across threads, you really, really, should be protecting it with a Condition or RLock. There is no guarantee in the language that, when thread 0 updates the value, thread 1 will see the new value immediately, or even ever. If you understand enough of the internals of (a specific version of) CPython, you can often prove that the GIL makes a lock unnecessary in specific cases. But otherwise, this is a race.

    Finally:

    The problem that I run into is that sometimes I feel like the time.sleep function in run , doesn't wait fully for a second (sometimes it may not even wait).

    Well, the documentation clearly says this can happen:

    The actual suspension time may be less than that requested because any caught signal will terminate the sleep() following execution of that signal’s catching routine.

    So, if you need a guarantee that it actually sleeps for at least 1 second, the only way to do this is something like this:

    t0 = time.time()
    dur = 1.0
    while True:
        time.sleep(dur)
        t1 = time.time()
        dur = 1.0 - (t1 - t0)
        if dur <= 0:
            break