How to Kill a Python Thread

Posted by
on under

I'm often asked how to kill a background thread, and the answer to this question makes a lot of people unhappy: threads cannot be killed. In this article I'm going to show you two options we have in Python to terminate threads.

A Threaded Example

To make this article more useful, let's use a simple example. Below is the complete code, which you can copy & paste and run on your computer with a name such as thread.py:

import random
import threading
import time

def bg_thread():
    for i in range(1, 30):
        print(f'{i} of 30 iterations...')
        time.sleep(random.random())  # do some work...

    print(f'{i} iterations completed before exiting.')

th = threading.Thread(target=bg_thread)
th.start()
th.join()

The only requirement for this application is Python, so there is no need to install any packages. You can start this application as follows:

$ python thread.py

Start the application and let it print a few lines. Before it gets to 30 lines, press Ctrl-C to interrupt it and note what happens:

~ $ python thread.py
1 of 30 iterations...
2 of 30 iterations...
3 of 30 iterations...
4 of 30 iterations...
5 of 30 iterations...
6 of 30 iterations...
7 of 30 iterations...
^CTraceback (most recent call last):
  File "thread.py", line 14, in <module>
    th.join()
  File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1011, in join
    self._wait_for_tstate_lock()
  File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1027, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt
8 of 30 iterations...
9 of 30 iterations...
10 of 30 iterations...
11 of 30 iterations...
12 of 30 iterations...
13 of 30 iterations...
^CException ignored in: <module 'threading' from '/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py'>
Traceback (most recent call last):
  File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1388, in _shutdown
    lock.acquire()
KeyboardInterrupt:

In the above run, I pressed Ctrl-C when the application reached the 7th iteration. At this point the main thread of the application raised the KeyboardInterrupt exception and wanted to exit, but the background thread did not comply and kept running. At the 13th iteration I pressed Ctrl-C a second time, and this time the application did exit.

Strange, isn't it? The problem is that Python has some logic that runs right before the process exits that is dedicated to wait for any background threads that are not configured as daemon threads to end before actually returning control to the operating system.

So the process received the interrupt signal in its main thread and was ready to exit, but first it needed to wait for the background thread to end. But this thread does not know anything about interrupting, all the thread knows is that it needs to complete 30 iterations before ending.

The wait mechanism that Python uses during exit has a provision to abort when a second interrupt signal is received. This is why a second Ctrl-C ends the process immediately.

As I mentioned in the introduction, threads cannot be killed, so what do you do? In the following sections I'll show you two options you have in Python to make the thread end in a timely matter.

Daemon Threads

I mentioned above that before Python exits, it waits for any threads that are not daemon threads. So what is a daemon thread? You may think I'm playing word games with you, but really the definition of a daemon thread is exactly that, a thread that does not block the Python interpreter from exiting.

How do you make a thread be a daemon thread? All thread objects have a daemon property. You can set this property to True before starting the thread, and then that thread will be considered a daemon thread.

Here is the example application from above, changed so that the background thread is a daemon thread:

import random
import threading
import time

def bg_thread():
    for i in range(1, 30):
        print(f'{i} of 30 iterations...')
        time.sleep(random.random())  # do some work...

    print(f'{i} iterations completed before exiting.')

th = threading.Thread(target=bg_thread)
th.daemon = True
th.start()
th.join()

Modify the application as indicated above, run it again, and try to interrupt it:

~ $ python x.py
1 of 30 iterations...
2 of 30 iterations...
3 of 30 iterations...
4 of 30 iterations...
5 of 30 iterations...
6 of 30 iterations...
^CTraceback (most recent call last):
  File "thread.py", line 15, in <module>
    th.join()
  File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1011, in join
    self._wait_for_tstate_lock()
  File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1027, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt

This time the first Ctrl-C causes the process to exit immediately.

So what happens to the thread? The thread continues to run as if nothing happened right until the Python process terminates and returns to the operating system. At this point the thread just ceases to exist. You may think that this is effectively a way to kill the thread, but consider that to kill threads in this way you have to kill the process as well.

Python Events

Using daemon threads is an easy way to avoid having to handle an unexpected interruption in a multithreaded program, but this is a trick that only works in the particular situation of the process exiting. Unfortunately there are times when an application may want to end a thread without having to kill itself. Also, some threads may need to perform clean up work before they exit, and daemon threads do not allow that.

So what other options are there? Since it is not possible to force the thread to end, the only other option is to add logic to it that voluntarily exits when requested. There are several ways to implement this type of solution, but one that I particularly like is to use an Event object.

The Event class is provided in the threading module of the Python standard library. You can create an event object by instantiating the class:

exit_event = threading.Event()

An event object can be in one of two states: set or not set. After creation, the event is not set. To change the event state to set, you can call the set() method. To find out if an event is set or not, you can use the is_set() method, which returns True if the event is set or False if not. It is also possible to "wait" for the event using the wait() method. A wait operation blocks until the event is set, with an optional timeout.

The idea is to set the event at the time when the thread needs to exit. The thread then needs to check the state of the event as often as it can, usually inside a loop, and handle its own termination when it finds that the event has been set.

For the example shown above a good solution is to add a signal handler that catches the Ctrl-C interruption, and instead of exiting abruptly, just set the event and let the thread end gracefully.

Below is a possible implementation of this solution:

import random
import signal
import threading
import time

exit_event = threading.Event()


def bg_thread():
    for i in range(1, 30):
        print(f'{i} of 30 iterations...')
        time.sleep(random.random())  # do some work...

        if exit_event.is_set():
            break

    print(f'{i} iterations completed before exiting.')


def signal_handler(signum, frame):
    exit_event.set()


signal.signal(signal.SIGINT, signal_handler)
th = threading.Thread(target=bg_thread)
th.start()
th.join()

If you try to interrupt this version of the application, everything looks much nicer:

~ $ python thread.py
1 of 30 iterations...
2 of 30 iterations...
3 of 30 iterations...
4 of 30 iterations...
5 of 30 iterations...
6 of 30 iterations...
7 of 30 iterations...
^C7 iterations completed before exiting.

Note how the interruption was handled gracefully and the thread was able to run the code that appears after the loop. This technique is very useful when the thread needs to close file handles or database connections before it exits. Being able to run clean up code before the thread exits is sometimes necessary to avoid leaks of resources.

I mentioned above that event objects can also be waited on. Consider the thread loop in the example above:

    for i in range(1, 30):
        print(f'{i} of 30 iterations...')
        time.sleep(random.random())

        if exit_event.is_set():
            break

In each iteration, there is a call to time.sleep(), which will block the thread. If the exit event is set while the thread is sleeping then it cannot check the state of the event, so there is going to be a small delay before the thread is able to exit.

In cases like this one, where there is sleeping, it is more efficient to combine the sleep with the check of the event object by using the wait() method:

    for i in range(1, 30):
        print(f'{i} of 30 iterations...')
        if exit_event.wait(timeout=random.random()):
            break

This solution effectively gives you an "interruptible" sleep, because if the event is set while the thread is stuck in the middle of the call to wait() then the wait will return immediately.

Conclusion

Did you know about event objects in Python? They are one of the simpler synchronization primitives and can be used not only as exit signals but in many other situations in which a thread needs to wait for some external condition to occur.

Do you want to learn another cool technique that uses event objects? My How to Make Python Wait article shows how to use an event to wait for a thread to end while showing a progress indicator!

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

31 comments
  • #1 Bogdan Stratila said

    Is a bad manner to run a background job in Flask application? And why?

  • #2 Miguel Grinberg said

    @Bogdan: Did I ever say it was bad manner? I don't think it is, this is actually a very used pattern.

  • #3 Luigi Cirocco said

    Very useful to know and thank you.

    recently I was using your flask-socketio example code incorporated with an MQTT subscriber over 4G modem as the background thread (time does not allow to describe in full) lest to say 4G modem traffic increased until the server machine re-boot, I now strongly suspect zombie threads may have been the cause.

    I'm wondering if you should make mention/incorporate what you presented here in the flask-socketio example in the background_thead() function (from memory) or is that breaking the separation of concerns?

    Thank you again for your consideration and clear communication.

    Lui

  • #4 Miguel Grinberg said

    @Luigi: I don't believe the example Socket.IO application suffers from potential zombie threads. That application is designed to start a single thread, never more.

  • #5 Luigi Cirocco said

    @Miguel, tl;dr: replying in the event it helps/alerts someone else with MQTT based IoT, no requirement for thoughtful response as I'm not developing this code any further at the moment.

    It is probably in the way I have cobbled the code for my particular application: I'm using the background_thread() function to set up a paho.mqtt.subscribe.callback(). I suspect the subscribe.callback() might be creating in it's own thread that connects to an MQTT broker on the other side of the 4G network, that does not die when the flask app terminates so the broker thinks it needs to keep publishing to a zombie that is maintaining a keep alive heart beat etc.

    I guess the easy way to check is to monitor the MQTT broker to see the list of subscribers once the app terminates. From memory there are ways of seeing a list of subscribers to any particular topic, .

    I'm still in the sandpit throwing things together and seeing whether my mental model matches the documented one.

    Lui

  • #6 Rodrigo Olmedo said

    Hello Miguel. First of all congratulations for your book Flask Web Development. It has helped me a lot in a recently finished project, a professional app to process tables.
    The application proceed with a very long numerical calculation process, lasting for 15 minutes in some extreme case. On this matter I have checked your page with the Celery example posted some years ago. Do you considered it as an updated version on the topic? Additionally you mentioned on that page something about sending a job to a background thread. Where can I find some more information about that option?
    Thanks again for your kind help.
    Rodrigo

  • #7 Miguel Grinberg said

    @Rodrigo: it seems you are asking me to compare two different solutions, but you have only mentioned the Celery one. What is the other solution you are considering? Sending a job to a background thread means to simply start a thread with your long calculation function. You can use the first code example in this article and replace the bg_thread() function with your code.

  • #8 Suresh said

    Hi Miguel, how to kill long running thread

    def bg_thread():
    #### long running DB query
    print(f'db operation completed before exiting.')

  • #9 Miguel Grinberg said

    @Suresh: you cannot kill a thread. I mention this in the article.

  • #10 Guy Bordelon said

    This WILL not work in Windows running Python under Spyder.

  • #11 Miguel Grinberg said

    @Guy: You need to define what "this" means. This article shows a bunch of possible solutions, not a single one. Also, Spyder is an IDE, which has nothing to do with stopping a thread. If your IDE does not let Python work in the way it was intended, then the problem is with the IDE, not with Python or this article.

  • #12 Andrei Enache said

    It worked for my needs. I have a GUI and a background thread that checks for new USB connections. Before setting daemon property to false, I was unable to kill the process by pressing the x button of the window. And if I execute Get-Process | where ProcessName -like 'py' I can see how the python process was still running consuming CPU time. Now it smoothly dies. Thanks

  • #13 Léon said

    Nice article, helped me - thanks!

  • #14 Max said

    I used your code with the "nice exit method" using the event. I just copied pasted it. But neither in spyder, nor using rare python interpreter from my windows 10 cmd terminal would react if I press Ctr + C....
    Any ideal why that is?

    Cheers,
    Max.

  • #15 Miguel Grinberg said

    @Max: I have not tested this code on Windows, I expect it would work the same, but I"m not sure. Signal handling is OS specific, so maybe something is different in how the Ctrl-C is handled. You may want to try replacing SIGINT with SIGBREAK to see if that helps. You may also want to try this under WSL.

  • #16 A said

    Just to let you know - really helpful!

  • #17 Alexandre Moncay Cechin said

    Does this happens also when using multiprocessing? Could I use the same solution to gracefuly kill the application using multiprocessing?

  • #18 Miguel Grinberg said

    @Alexandre: This is for threads only.

  • #19 Alexey Obukhov said

    hello Miguel,

    your post gave me a kick:) I found another simple solution for my case in just writing os._exit(0) in signal_handler() to handle it

  • #20 Arun said

    Catching signal events doesn't seem to work in windows and I am not alone https://github.com/LonamiWebs/Telethon/issues/947

  • #21 Miguel Grinberg said

    @Arun: signals are not as well supported on Windows as they are on Unix based OSes, that is correct.

  • #22 Meghanath said

    Hello Miguel,

    I have developed a RestAPI Post method in flask which triggers another function. The function takes a bit of time to complete the processing. So I created a thread to trigger the function and have the API return a value immediately like "Action_Accepted". I have a return statement in the function being called. So does the thread terminate if it gets to the return statement? I am not sure how to add thread.join() here since the Post method immediately returns a value to the caller. Please let me know

  • #23 Miguel Grinberg said

    @Meghanath: the thread will continue running in the background after your request handler returns. You will not be able to join the thread from this request handler, simply because the handler is going to end before the thread.

  • #24 Miguel Lopez said

    What happens with the threads that were not killed?

    Are they continue running on the background?
    If the script finishes but threards were not killed, what happends to them?

  • #25 Miguel Grinberg said

    @Miguel: Well, threads cannot be killed, so your question as posted does not make sense. If you mean to ask what happens with threads that you do not gracefully stop, then it really depends. If the thread is configured as a daemon thread, it will just stop running abruptly when the Python process ends. If the thread is not a daemon thread, then the Python process will block while trying to exit, waiting for this thread to end, so at some point you will have to hit Ctrl-C to kill the process forcefully.

Leave a Comment