Thread Life cycle in Python

The smallest unit of execution inside a process is called a thread life cycle in Python. Threads share the main program’s runnable resources, including memory, and function in the same context. Every thread can generate a result and has a specified starting point and execution order. To work with threads, Python has the threading module. Threads inside the same Python process share memory, in contrast to processes, which normally have their own memory space.
We may explain the normal course of a thread’s execution using the facts and examples given.Formal “thread life cycle” with named states like some other programming languages might.
Creation
Although a thread has been established, it has not yet begun to run. In order to do this, a Thread instance must be created, frequently with a target function or method specified for the thread to execute. As an alternative, you can subclass threading to build a custom class.replacing its run method with a thread. The thread’s actions are specified by the run method.
Starting
The start() function of the thread is called to begin its execution. After preparing the thread, this method invokes the object’s run method or the target function in a different thread of control.
Running
After starting, the thread runs the code specified in its run method or target function. This code executes alongside other threads and the main application.
Waiting/Blocking
A thread may get into one of two states: blocking or waiting. This may occur when it executes tasks like accessing shared resources (maybe awaiting a lock), waiting for input/output (I/O), or actively halting execution with methods like time.sleep(). Waiting explicitly with time.Examples frequently utilise sleep() to mimic work or keep the main thread from ending too soon. The Global Interpreter Lock (GIL) in Python allows threads waiting on I/O to release it and let others run. For CPU-bound activities, only one thread can execute Python bytecode.
Termination
When the run method or target function is finished, a thread ends its execution. Depending on whether the thread is intended to terminate based on an external signal, like checking an internal flag within a loop, or if an exception is generated and not caught, this can occur prematurely or normally after the code finishes. To make the caller thread (often the main thread) wait until that particular thread completes its execution, the join() method can be performed on a thread object.
This helps to prevent the main program from ending before the child threads are finished. In the absence of join() or comparable methods (such as time.sleep() in the main thread), the main thread may terminate, which on some systems may result in the termination of the child threads before they have finished their task.
Standard programming constructs govern the thread’s execution flow during the operating phase. When doing recurring activities within a thread’s execution, loops like while and for loops are frequently used. These loops can have their execution flow changed by utilising control statements like break and continue. While a continue statement skips the remainder of the current loop iteration and moves on to the next, a break statement ends the deepest loop it is in. Within a block of code, the pass statement serves as an empty placeholder.
Example:
import threading
import time
import os
# Function to be executed by the thread
def thread_task(name, count):
"""
This function represents the work done by a thread.
It runs a loop, prints status, and simulates work.
"""
# --- Running State Begins ---
print(f"Thread {name}: Started in process {os.getpid()}")
start = 1
# Use a loop (while or for) for repetitive tasks within the thread
# This loop simulates the core work of the thread
for i in range(start, count + 1):
print(f"\tThread {name}: Iteration {i} of {count}. Doing work...")
# --- Waiting/Blocking State ---
# time.sleep() simulates a blocking operation or work
# This pauses the thread's execution
time.sleep(0.5)
# Example of using a control statement inside the loop (optional)
# if i == 5:
# print(f"\tThread {name}: Breaking loop at iteration 5.")
# break # Exits the for loop
print(f"Thread {name}: Finished.")
# --- Termination State ---
# Main part of the program
if __name__ == "__main__":
# --- Main Thread Execution ---
print(f"Main thread: Started in process {os.getpid()}") # Identify main process
# --- Thread Creation ---
# Create Thread instances, specifying the target function and its arguments
thread1 = threading.Thread(target=thread_task, args=("Alpha", 5))
thread2 = threading.Thread(target=thread_task, args=("Beta", 3))
# --- Thread Starting ---
# Call the start() method to begin thread execution
print("Main thread: Starting threads Alpha and Beta...")
thread1.start()
thread2.start()
print("Main thread: Threads started.")
# --- Main thread could do other work here while Alpha and Beta are running ---
# print("Main thread: Doing other work...")
# time.sleep(1) # Main thread also simulates work or waiting
# --- Waiting for Threads (Joining) ---
# Use join() to wait for threads to complete.
# This ensures the main thread doesn't exit before the child threads
print("Main thread: Waiting for threads to complete...")
thread1.join() # Main thread waits for thread1 to finish
thread2.join() # Main thread waits for thread2 to finish
print("Main thread: All threads finished.")
print("Main thread: Exiting.")
# --- Main Thread Exiting ---
Output:
Main thread: Started in process 4990
Main thread: Starting threads Alpha and Beta...
Thread Alpha: Started in process 4990
Thread Alpha: Iteration 1 of 5. Doing work...
Thread Beta: Started in process 4990
Thread Beta: Iteration 1 of 3. Doing work...
Main thread: Threads started.
Main thread: Waiting for threads to complete...
Thread Beta: Iteration 2 of 3. Doing work...
Thread Alpha: Iteration 2 of 5. Doing work...
Thread Beta: Iteration 3 of 3. Doing work...
Thread Alpha: Iteration 3 of 5. Doing work...
Thread Beta: Finished.
Thread Alpha: Iteration 4 of 5. Doing work...
Thread Alpha: Iteration 5 of 5. Doing work...
Thread Alpha: Finished.
Main thread: All threads finished.
Main thread: Exiting.
Explanation of the Code and Life Cycle
Main Thread Start: “Main Thread Start” is the first line of execution in Python or any application. The “main thread.” is the primary thread of every process. It sets up the program’s environment, executes top-level script code, and, critically, manages any “child threads” or “worker threads” it creates. The main thread starts when the program starts, executes instructions sequentially, and usually runs until all non-daemon child threads have finished, then terminates, exiting the program.
Thread Creation: Two threadings are created.Thread1 and Thread2 are thread objects. The function thread task is passed as the target when the thread objects are created, along with a tuple of arguments (args) that the function will receive. The threads have been created, but they are not yet active.
Thread Starting: We invoke thread1.start() and thread2.start() to begin the thread. The thread is set up and the thread task function (the target) is scheduled to execute in a new thread of execution using the start() method. These new threads are actually started and scheduled by the operating system. When start() is called, the main thread instantly resumes its execution.
Thread Running: In the newly created threads, the thread task function starts running.
- Every thread prints a message confirming its initiation inside thread task.
- The thread carrying out repeating tasks is represented by a for loop. For iteration, loops are essential control flow structures.
- To indicate that the thread is actively operating, the current iteration is displayed inside the loop using print(). It should be noted that if many threads write to the same output (such as the console), their outputs may be synchronised or interleaved.
- It calls time.sleep(0.5). This results in a 0.5-second execution halt for the active thread. The Python interpreter can switch to and run another thread while one is asleep (in a Waiting/Blocking state) because the GIL handles I/O-bound operations like waiting. Concurrency is made possible at this point when threads relinquish control.
- For the designated number of count iterations, the loop keeps running.
Thread Termination: Thread termination ends program thread execution. Most threads terminate gracefully when their target function returns or when they purposely break an indefinite loop. Unhandled exceptions can potentially cause a thread to terminate immediately. The Python interpreter terminates “daemon” threads without notice or cleanup when the main application thread ends and no other non-daemon threads are active. This makes daemon threads ideal for background operations that do not require program shutdown cleanup or resource release. The application does not terminate until non-daemon threads finish, ensuring vital tasks are accomplished.
Thread Joining: Thread1.join() and thread2.join() are called back in the main thread. Until the corresponding thread (thread1, then thread2) completes, the join() method stops the main thread from running. This is important because, in the absence of join(), the main thread would probably finish its loop before the child threads did, which could lead some systems to terminate them too soon. The main thread eagerly awaits the completion of the thread task by the Alpha and Beta threads after joining.
Main Thread Exit: The “Main Thread Exit” finish a program’s execution from the primary thread’s perspective. After executing all its instructions, including calls to join other threads, the main thread does this. In multithreaded programs, the main thread exit must wait for all non-daemon child threads to finish before exiting, but not daemon threads. The Python interpreter will suddenly cease any daemon threads after the main thread and all non-daemon threads finish.
This example shows the path a thread takes from definition, creation, and start to active task execution (including waiting periods), job completion, and termination, while the main program waits for the thread to finish.Synchronisation techniques like locks (threading.Lock) are required for managing shared resources between threads in order to avoid data corruption.