Where does the thread go when the thread goes out?

@cyclicalobsessive

Of course, this topic could have been titled:
(bad pun warning!)
:musical_note:  Baby, baby, baby, where did our thread go?  :musical_note:

Given a Python program that creates and spins off a thread

[. . . some code. . .]
my_thread = Thread(target=some_process)
[. . . more code. . .]

. . . and everything is happening just wonderfully.  The program is humming along and the thread is happily threading along too. . .

Until. . . something isn’t wonderful and an exception is thrown somewhere else in the program, (not the thread), with all the traceback fanfare about an issue that has nothing to do with the thread.

What happens to the thread?  Does the rob-rob-robin keep bob-bob-bobbin’ along?  Or does the thread automatically get the axe when the main process spilled its guts?

2 Likes

If the main() is well designed, (try, except, finally with a stop thread flag var, the except will handle the exception and the finally block will tell the thread to stop, and wait at a join() for the thread to complete.

If an exception in main is not handled, causing main to exit, the thread will just keep happily doing its thing until it completes or has an unhandled exception. (Or is killed by the user - ps -ef | grep progname followed.by kill -9 nnnn)

I have some examples to show exceptions in a thread, with and without a join in main, as well as exceptions in main, with and without catch and finally join - if you want to delve into the three good design, and three bad design cases.

2 Likes

I would love to see them, with your commentary of course.

Doesn’t join() kill the thread?  or do you have to do that separately?

P.S.
I saw

And on my phone’s tiny screen it looked like “kill -9mm”! :astonished: :wink:

2 Likes

No, join() is where a good main() waits for the thread to exit (either finished, flagged to finish, or exception finished)

Good design will include a way to tell the thread to quit on demand.

2 Likes

Damned documentation!

I read about threading and several sites, (that I don’t remember right now), mentioned that the thread should be “killed with a join()”.

I had my doubts because both this code and other examples included specific “kill [with a] 9mm [hollow-point]” functionality.  This is why I asked this specific question.

Another question:

  • Assume the programs as noted above where the main routine spins off a thread that wanders off and does whatever it wants.
  • Also assume that something happens to the thread - it abends, it gets stuck, it whatever’s in such a way that it never returns.

The main routine sends a polite “kill” (sigterm) request and waits at the “join()” pub to have a beer.

In this case, the thread never returns or responds.  What happens?  Does the main routine spend all night at the pub waiting for a thread that will never show up?  Do I have to include a “Humm. . . it’s getting a bit late.” routine to the original command to shut down the thread that will send it a “kill (9mm Glock)” message if it never returns to make sure it isn’t hanging out with a bad crowd?

1 Like

If it “abends” it ends.

Threads “end or finish”, not “return”.

So in the case of a thread that will not end (or finish)

The user will have to open another terminal and kill both the thread thread and possibly the main thread. The main may end naturally when the user kills the thread, but there are sometimes zombie threads that only clear from the process list via a reboot

2 Likes

Then what’s the purpose of the “join()”?

1 Like

Poor thread.  It must be stuck on the “T” with Charlie - “The Man Who Never Returned

The Kingston Trio recorded this in response to the “T”, (Boston’s mass transit system), wanting to impose an “exit fare” at certain stations.  In this song, the character, (Charlie), didn’t have the extra money required to leave the station so he was “stuck” on the subway and became “The Man Who Never Returned”.

Back in the early 2000’s, the Boston Transit Authority - (the “T”), instituted a reusable “proximity” type card that could store value and be used to pay the turnstile fare - and it became known as the “Charlie Card”.

(I think I have one kicking around somewhere. . .)

P.S.
They had to change their name from the “MTA” as sung in the song, because that was already taken by the New York Subway’s transit authority.

1 Like

Absolutely my favorite group of childhood - learned to play guitar chords with their songs. My “mix tape” (record stack) was Harry Belafonte, Kingston Trio, Mothers Of Invention, Peter Paul And Mary.

2 Likes

Allow re-syncronization with main, allow “returning something” to main. The join() function of a thread actually runs in the caller’s thread - the main() thread.

This is an example of:

  • printing a thread exception and traceback in the thread
  • then passing the exception back to the main via the join()
$ ./handle_thread_exception_in_main.py 
09:26:47: Thread-1 Causing an exception in thread 
09:26:47: Thread-1: Printing traceback in the thread exception handler
  File "/usr/lib/python3.7/threading.py", line 885, in _bootstrap
    self._bootstrap_inner()
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "./handle_thread_exception_in_main.py", line 30, in run
    traceback.print_stack()
09:26:47: MainThread: re-raising exception in thread.join()
09:26:47: Exception Handled in Main, Details of the Exception: division by zero

The program:

#!/usr/bin/env python3

# Handle a thread exception in the main

# Importing the modules
import threading
import traceback
import logging


# Custom Thread Class
class MyThread(threading.Thread):

  # Function that raises the exception
    def someFunction(self):
        name = threading.current_thread().name
        logging.info("%s Causing an exception in thread ",name)
        divbyzero=1/0

    def run(self):

        # Variable that stores the exception, if raised by someFunction
        self.exc = None
        try:
            self.someFunction()
        except Exception as e:
            name = threading.current_thread().name
            logging.info("%s: Printing traceback in the thread exception handler")
            traceback.print_stack()

            # now save the exception for later re-raising to the main
            self.exc = e

    def join(self):
        threading.Thread.join(self)
        # Since join() returns in caller thread
        # we re-raise the caught exception
        # if any was caught
        if self.exc:
            name = threading.current_thread().name
            logging.info("%s: re-raising exception in thread.join()", name)
            raise self.exc

# MAIN function
def main():

    # set up logger
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")


    # Create a new Thread t
    # Here Main is the caller thread
    t = MyThread()
    t.start()

    # Exception handled in main thread
    try:
        t.join()
    except Exception as e:
        logging.info("Exception Handled in Main, Details of the Exception: %s", e)

if __name__ == '__main__':
    main()
1 Like

The trouble is that good design means putting error handlers all over the place to gracefully clean up any unexpected mess that might occur as well as expected exceptions like control-c.

The example is a personal “good enough design IMO” and I am not a Python expert, so it is probably not a good design. Additionally, if the thread purpose is to wait for I/O, (the main reason for threading usually), the thread design requires even more complexity - “blocking I/O with timeout”, of which there is an example here.

Threads introduce complexity, and Python’s Global Interpreter Lock (GIL) complicates the thinking with threads even more by making only one thread execute at a time.

Threading: Best for allowing program to remain active while also waiting for I/O
MultiProcessing: Best for spreading computationally intensive tasks across cores of processor for true simultaneous execution

So here is my messy-ness

EVEN BETTER - STUDY THIS EXCELLENT TUTORIAL:

2 Likes