[Tutor] Killing threads

Tobin, Mark Mark.Tobin@attcanada.com
Thu, 21 Jun 2001 06:40:44 -0600


Allan, 

Thanks for the solution... the code works now, but even more importantly I
am starting to get a handle on threading.  The threading.Condition() was
just what I knew had to be there somewhere but couldn't find because I
wasn't exactly sure what it should look like.

Again thanks for the effort you put into helping me out... I think this
newsgroup/listserve is the best thing (among many) that Python has going for
it.

Mark

> -----Original Message-----
> From:	Allan Crooks [SMTP:allan.crooks@btinternet.com]
> Sent:	Wednesday, June 20, 2001 6:21 PM
> To:	Mark.Tobin@attcanada.com; tutor@python.org
> Subject:	RE: [Tutor] Killing threads
> 
> Hi,
> 
> Here we go with a solution. :)
> 
> # Your code
> def main():
>     a = Account()
>     a.arp = [300]
>     a.threads = []
>     t = threading.Thread(target = a.checker, args = (a.arp))
>     a.threads.append(t)
>     u = threading.Thread(target = a.menu, args = ())
>     a.threads.append(u)
>     a.nthreads = range(len(a.threads))
>     for i in a.nthreads:
>         a.threads[i].start()
>     for i in a.nthreads:
>         a.threads[i].join()
>         
> Given that we don't have the Account class at hand, it's somewhat more
> problematic to show you a corrected version specifically for your code.
> 
> Note that I said it was "problematic", not "impossible". :)
> 
> First of all though, I'll point out a few things in your code.
> 
> #    t = threading.Thread(target = a.checker, args = (a.arp))
> 
> You may or may not intend this, it's hard to say, but if you want to
> supply a tuple with "a.arp" as an argument, you can put:
>   args = (a.arp,)
>   
> Otherwise Python doesn't realise you mean a tuple. For any variable value
> x, (x) is the same as x.
> 
> 
> 
> Secondly, the for loops can be written like this:
> 
>     for x in a.threads:
>        x.start()
>        
> It's a lot easier to read (and is one of the reasons I prefer Python over
> Java! :)
> 
> Now back to your question, how to stop threads.
> 
> Python doesn't provide a way to stop threads. The "threading" module is
> made to resemble Java's Thread objects, which supports the "stopping" of
> threads. But Python doesn't support it (for good reasons), so we have to
> simulate it ourselves.
> 
> There are two ways of doing this:
>   1) Extending Thread objects.
>   2) Using Condition objects.
>   
> Since I can only be bothered to write one solution, I'll go for the second
> one. :)
> 
> A condition object is a type of lock, which allows threads to use it as a
> signalling area. An appalling description, but it's the best I can come up
> with.
> 
> > One of the threads, u, is active and provides a (so far) basic interface
> for
> > the user through the menu method of the class account.  The second, t,
> is
> > generally dormant, but pops up once in a while to check a single flag,
> then
> > falls back to sleep.  I need for the active, u, thread to be able to
> kill
> > both threads by calling another function quitter().
> 
> Both threads will need to use the Condition object to stop themselves.
> 
> So let us assume that in the Account object, you have a field, "cond",
> which is the Condition object. We'll also have another field which
> indicates we want threads to stop.
> 
> # a.cond = Condition()
> # a.halt = 0
> 
> The menu function needs to stop both threads. The checker function needs
> to be able to be told to stop.
> 
> So first, I'll write the checker function.
> 
> def checker(self):
>    while not self.halt:
>       # check flag, perhaps exit or do whatever
>       self.cond.acquire()
>       if not self.halt:
>           self.cond.wait(x) # x is some value of seconds
>       self.cond.release()
>          
> There are several different ways of writing this code (I've written at
> least 4 different versions), but I'll settle on this one. It may look odd
> that we're seeing if self.halt is tested twice, but hopefully it should
> make sense.
> 
> First of all, the checker function will run until it has been told to
> stop.
> 
> It performs the check it is intended to do. It then "acquires" the
> condition object, which means that there is no way somebody can be running
> code in another thread that has "acquired" the condition object.
> 
> If you've ever written threading code in java, the acquire / release
> methods are similar to the synchronized keyword. If you haven't, then it
> doesn't matter.
> 
> The reason we acquire the condition object, is because we need to be able
> to "wait" on it. You mentioned the checking thread sleeps. However, for
> the other thread to stop the checking thread, we need to tell the checking
> thread to stop.
> 
> 
> 
> It still may not be clear what the condition object does, or why we use
> it. What it does will be explained a bit later, but I'll explain why we
> use it.
> 
> As you can see, we are using a flag (halt) to tell the thread to stop. And
> there are two ways of doing this without condition objects.
> 
> The first is written like this:
> 
> def checker(self):
>    while not self.halt:
>       # check flag, perhaps exit or do whatever
> 
> Much simpler I'm sure you'll agree. The only problem is that the thread
> doesn't check "every now and then", but all the time. You could adjust the
> code to figure out how much time has elapsed since the last check and then
> execute it if a certain amount of time has elapsed. But that still doesn't
> do what you want, the thread is continously running the loop and using
> lots of processor time, even though it's just waiting for a certain amount
> of time to elapse. Which is a bad thing.
> 
> 
> 
> So to combat this, we should make the thread sleep. We can use the sleep
> function in the time module to pause.
> 
> Here's the second version:
> 
> def checker(self):
>    while not self.halt:
>       # check flag, perhaps exit or do whatever
>       time.sleep(x) # x is however long you want it sleep for
>       
> However, say the thread starts to sleep 20 secs, and as soon as it starts
> sleeping, the other function tells it to stop. But the checker thread
> won't respond until it's woken up.
> 
> So we need a way for the thread to sleep, but to wake up if it's being
> told something.
> 
> 
> Solution? Condition objects. :)
> 
> 
> Back to the code I wrote:
> 
> def checker(self):
>    while not self.halt:
>       # check flag, perhaps exit or do whatever
>       self.cond.acquire()
>       if not self.halt:
>           self.cond.wait(x) # x is some value of seconds
>       self.cond.release()
>       
> 
> So hopefully it should be clearer *why* we are using Condition objects.
> The other thread can wake the checker thread up, even if it was sleeping.
> 
> The wait method makes the thread sleep until someone wakes it up. We can
> either write:
>    self.cond.wait() # or
>    self.cond.wait(x)
>    
> The first wait will simply wait for eternity until it is woken up by
> another thread. But since we still want to wake up every now and then and
> check whatever flag we're checking, we write the second version. This
> means that by the time that line of code has been executed, it has either
> been woken up by another thread, or it has slept for x seconds without
> being woken up by the other thread.
> 
> 
> Why the two checks of self.wait? Well, that's quite simple. Here's how it
> would look otherwise:
> 
> def checker(self):
>    while not self.halt:
>       # check flag, perhaps exit or do whatever
>       self.cond.acquire()
>       self.cond.wait(x) # x is some value of seconds
>       self.cond.release()
>       
>       
> This looks OK, but during the time we have been checking flags, we have
> been told to halt. But it would still go to sleep.
> 
> We could make the while loop an infinite loop, but it makes this:
> 
> def checker(self):
>    while 1:
>       # check flag, perhaps exit or do whatever
>       self.cond.acquire()
>       if not self.halt:
>           self.cond.wait(x) # x is some value of seconds
>           self.cond.release()
>       else:
>           self.cond.release()
>           break
>           
> We still need to release the condition object even if we quit the loop.
> This should work, it's just the other version looks nicer.
> 
> 
> OK, hopefully that's clear. Now we come to define the menu function.
> 
> def menu(self, *whatever_args_you_want):
>    # do menu stuff
>    # this bit afterwards is to stop other threads
>    self.cond.acquire()
>    self.halt = 1
>    self.cond.notifyAll()
>    self.cond.release()
>    
> This should be fairly straight forward to understand. The "notifyAll"
> method will simply wake up all threads which are sleeping (waiting on the
> condition object). You also have the "notify" method, which will wake up
> only one thread. It doesn't matter which one you do in this example.
> 
> I think that should work, I've got no code to test it on and no patience
> to write one myself.
> 
> If you were to have more than two threads, would this approach still work?
> It should do. All functions which do anything will have to look like
> checker (so they can respond to exits).
> 
> 
> I'm ever so slightly dubious about what I've written, I presumed I
> would've written more, but I haven't. If this doesn't work, let us know.
> 
> Allan.