[Tutor] Killing threads

Allan Crooks allan.crooks@btinternet.com
Wed, 20 Jun 2001 23:21:14 +0100


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.