[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.