Kurt Peek Kurt Peek - 1 month ago 5
Python Question

Example usages of the StoppableThread subclass of Python's threading.Thread

I'm trying to understand a highly-upvoted solution to the problem of killing a thread in Python: Is there any way to kill a Thread in Python?. The author provides a class

StoppableThread
and describes in words how to use it (namely, by calling its
stop()
method and waiting for the thread to exit properly using
join()
).

I've tried to make this work using a simple example:

import time
import threading

class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""

def __init__(self, *args, **kwargs):
super(StoppableThread, self).__init__(*args, **kwargs)
self._stop = threading.Event()

def stop(self):
self._stop.set()

def stopped(self):
return self._stop.isSet()

def run_forever():
while True:
print("Hello, world!")
time.sleep(1)

thread = StoppableThread(target=run_forever)

thread.start()
time.sleep(5)
thread.stop()
thread.join()


I would expect the program to print
Hello, world!
approximately 5 times before the
thread
is stopped. However, what I observe is that it just keeps on printing indefinitely.

Can someone clarify the correct usage of the
StoppableThread
subclass?

Answer

The problem is commented there in red. You do not check in your while loop whether the stopped() condition occurs. You can only use this to deliver a "message" to the thread that it should quit now, but it doesn't do that automatically.

You should add an if clause in your while loop and exit if you detect the stopped condition.

So do this and it should work:

import time
import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self, *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop = threading.Event()

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()

    def run(self):
        while True:
            if self.stopped():
                return
            print("Hello, world!")
            time.sleep(1)

thread = StoppableThread()

thread.start()
time.sleep(5)
thread.stop()
thread.join()

or you can subclass StoppableThread:

import time
import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self, *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop = threading.Event()

    def stop(self):
        self._stop.set()

    def stopped(self):
        return self._stop.isSet()


class pup(StoppableThread):
    def __init__(self, *args, **kwargs):
        super(pup, self).__init__(*args, **kwargs)

    def run(self):
        while True:
            if self.stopped():
                return
            print("Hello, world!")
            time.sleep(1)

thread = pup()

thread.start()
time.sleep(5)
thread.stop()

thread.join()

If your thread is something that does work and then sleeps, and loops until ad nauseam, this is not a very good method, as it would not detect the condition while sleeping. If you have long sleeps in your threads, they need to expire before threads quit.

I have in these cases used queues instead. Before launching the thread I create a queue and pass this to the thread as a parameter. I replace a sleep(30) with Queue.get(True, 30), which either sleeps for 30 seconds or until there is a message. Then I can act on this message, either shut down or do other things if I need to communicate with the thread more deeply.

Hannu