What happens if I don't join() a python thread?
Asked Answered
G

4

12

I have a query. I have seen examples where developers write something like the code as follows:

import threading
def do_something():
    return true
t = threading.Thread(target=do_something)
t.start()
t.join()

I know that join() signals the interpreter to wait till the thread is completely executed. But what if I do not write t.join()? Will the thread get closed automatically and will it be reused later?
Please let me know the answer. It's my first attempt at creating a multi-threaded application in Python 3.5.0.

Goodtempered answered 10/2, 2020 at 10:13 Comment(0)
J
13

A Python thread is just a regular OS thread. If you don't join it, it still keeps running concurrently with the current thread. It will eventually die, when the target function completes or raises an exception. No such thing as "thread reuse" exists, once it's dead it rests in peace.

Unless the thread is a "daemon thread" (via a constructor argument daemon or assigning the daemon property) it will be implicitly joined for before the program exits, otherwise, it is killed abruptly.

One thing to remember when writing multithreading programs in Python, is that they only have limited use due to infamous Global interpreter lock. In short, using threads won't make your CPU-intensive program any faster. They can be useful only when you perform something involving waiting (e.g. you wait for certain file system event to happen in a thread).

Janitor answered 10/2, 2020 at 10:19 Comment(14)
Please let me know whether the thread get released even I am not writing the .join()? Will it affect the programming logic?Will it make the process slow?Goodtempered
@JafferWilson It's not clear what you mean by releasing a thread. It will be destroyed eventually. It certainly might affect the program logic depending on how you access shared resources.Janitor
I mean will it impact the program badly. I mean will it make the program work slower than what it was previously? Or any other side effects of not using the join()?Goodtempered
@JafferWilson if you're creating just one thread and then join it, it is quite the same as just calling the target function. If you're not joining it, then it all depends on what actually happens in the target function. I updated the question with some performance-related concerns.Janitor
@bereal, Re, "not clear what you mean by releasing a thread." That's the heart of the question. In a typical OS, after a thread has "died," it continues to use some system resources (e.g., a thread-ID and/or a "descriptor") until the program explicitly releases them. In some programming systems (e.g., Java) the library automatically releases the dead thread, and join() literally does nothing except wait for the thread to "die." In others (e.g., C++), releasing the descriptor happens inside the join() call. OP is asking which way it works in Python. (Sorry OP, I personally don't remember.)Fundus
so even if we don't join thread. the thread will be deleted after the function completes?Dudgeon
@MojixCoder yes, if you mean the thread target function.Janitor
no I meant the actual thread. even if we don't join() thread. will it be closed? @JanitorDudgeon
@MojixCoder sorry, I don't understand. Thread terminates when it function exits. Or when the process ends (if it's a daemon thread).Janitor
so why we do use .join() on threads? just to wait for their task to finish? is it okay not to use .join() on threads when we don't want to wait? @JanitorDudgeon
@MojixCoder yes, it's normally ok not to use join() if you don't want to wait.Janitor
"A Python thread is just a regular OS thread." is this not misleading? Then, what is a Python "Process"?Sumba
@Sumba no, it's not. A Python "process" is a regular OS process.Janitor
@Janitor thanks! earlier I was thinking python threads had no appearance in OS. I actually came here to delete the comment, but you beat me.Sumba
W
8

The join part means the main program will wait for the thread to end before continuing. Without join, the main program will end and the thread will continue.

Now if you set the daemon parameter to "True", it means the thread will depends on the main program, and it will ends if the main program ends before.

Here is an example to understand better :

import threading
import time

def do_something():
    time.sleep(2)
    print("do_something")
    return True

t = threading.Thread(target=do_something)
t.daemon = True # without the daemon parameter, the function in parallel will continue even your main program ends
t.start()
t.join() # with this, the main program will wait until the thread ends
print("end of main program")

no daemon, no join:

end of main program
do_something

daemon only:

end of main program

join only:

do_something
end of main program

daemon and join:

do_something
end of main program
# Note : in this case the daemon parameter is useless
Western answered 10/2, 2020 at 10:19 Comment(1)
This doesn't help, especially since you're setting .daemon = True, which changes how threads work when the main program is quitting.Deliquesce
D
2
  • Without join(), non-daemon threads are running and are completed with the main thread concurrently.

  • Without join(), daemon threads are running with the main thread concurrently and when the main thread is completed, the daemon threads are exited without completed if the daemon threads are still running.

You can see my answer in this post explaining about it in detail.

Dreiser answered 27/10, 2022 at 15:26 Comment(0)
M
1

With Python 3.10.12, nothing bad happens, if you don't join() threads. To test, I used this code:

#! /usr/bin/python3

import logging
import threading
import time

def thread_function(name):
    logging.info("Thread %2s: starting", name)
    time.sleep(20)
    logging.info("Thread %2s: finishing", name)

def daemon_thread_function():
    logging.info("Daemon   : starting")
    i = 0
    while True:
        time.sleep(1)
        i = i + 1
        logging.info("Daemon   : %s seconds have passed", i)

if __name__ == "__main__":
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")

    for i in range(1, 51):
        logging.info("Main     : creating and running thread %2s", i)
        threading.Thread(target=thread_function, args=(i,)).start()

    logging.info("Main     : creating and running daemon thread")
    threading.Thread(target=daemon_thread_function, args=(), daemon=True).start()

    wait = 40
    logging.info("Main     : sleeping %s seconds", wait)
    time.sleep(wait)
    logging.info("Main     : finished waiting")

In the first 20 seconds after starting this code, you can use ps -eLf, ls /proc/MAINPROCESSID/task and many other commands to verify, that there are actually 52 threads running in this test application: The main thread, the 50 foreground children threads and the daemon thread. 20 seconds after starting the application, the foreground children threads all exit and only two threads are left: The main thread and the daemon thread. ps -eLf doesn't show any "zombie threads" with unread status or similar, so no OS resources are blocked by the unjoined threads!

And when after 40 seconds total time, the main thread exits as well, the daemon thread also finishes and the python app exits. So the unjoined threads also do not block any resources internally in Python, in particular, they do not keep daemon threads running unnecessarily.

Just one recommendation: If you plan to never join threads, do not keep references to the threads created around, so that the python interpreter can do it's job and garbage collect all resources related to all finished threads. The syntax in the test application threading.Thread(target=..., args=...).start() is one possible way to achieve this "fire and forget" semantics.

Of course, make sure to use other synchronization methods (like threading.mutex or threading.semaphore) to keep track of your threads' work.

Mada answered 6/11, 2023 at 20:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.