1

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?

Community
  • 1
  • 1
Kurt Peek
  • 52,165
  • 91
  • 301
  • 526

1 Answers1

4

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

Hannu
  • 11,685
  • 4
  • 35
  • 51
  • Can you perhaps include the entire code? If I make `run_forever` a class method of `StoppableThread` (necessary to use the `self`) reference I get a `NameError` when trying to do `thread = StoppableThread(target=run_forever)`. – Kurt Peek Nov 02 '16 at 14:33
  • I have added the complete code. I just realised you had renamed your run method and placed it outside the thread. The above (probably) works. You also do not need the target keyword parameter. – Hannu Nov 02 '16 at 14:36
  • I added another code snippet where you subclass StoppableThread if you plan to have several stoppable threads in your application. – Hannu Nov 02 '16 at 14:49