UNIT No - III
Threads in Python
A thread is the smallest unit of execution within a process. It allows a program to run multiple
operations concurrently, improving efficiency and performance. Python provides threading
support through the threading module, enabling multitasking within a single program.
Creating and Using Threads in Python
Python's threading module allows the creation of threads using the Thread class.
Example: Creating a Thread in Python
python
CopyEdit
import threading
def print_numbers():
for i in range(5):
print(f"Number: {i}")
# Creating a thread
t1 = [Link](target=print_numbers)
# Starting the thread
[Link]()
# Waiting for the thread to complete
[Link]()
print("Thread execution completed!")
Benefits of Using Threads in Python
1. Faster Execution (Concurrency)
o Threads run concurrently, meaning multiple tasks can be performed
simultaneously.
o Ideal for tasks that involve waiting, such as I/O operations, network calls, or file
reading.
2. Efficient CPU Utilization
o If one thread is waiting for a response (e.g., downloading a file), another thread
can execute a different task.
o This optimizes system resource usage.
3. Better Responsiveness
o In GUI applications, using threads prevents the interface from freezing while
performing background tasks.
4. Parallel Execution of I/O Bound Tasks
o Python’s Global Interpreter Lock (GIL) restricts true parallel execution for CPU-
bound tasks.
o However, threads are highly effective for I/O-bound operations like reading files,
making API calls, or database queries.
5. Simplifies Complex Programs
o Multithreading allows breaking down large tasks into smaller, manageable sub-
tasks.
o
Difference between Process and Thread
Process Thread
Process means a program in execution. Thread means a segment of a process.
A process takes more time to terminate. A thread takes less time to terminate.
It takes more time for creation. It takes less time for creation.
It also takes more time for context
It takes less time for context switching.
switching.
A process is less efficient in terms of Thread is more efficient in terms of
communication. communication.
We don’t need multi programs in action for
Multiprogramming holds the concepts of
multiple threads because a single process
multi-process.
consists of multiple threads.
Every process runs in its own memory. Threads share memory.
A process is heavyweight compared to a A Thread is lightweight as each thread in a
thread. process shares code, data, and resources.
Thread has Parents’ PCB, its own Thread
A process has its own Process Control
Control Block, and Stack and common
Block, Stack, and Address Space.
Address space.
Since all threads of the same process share
Changes to the parent process do not affect address space and other resources so any
child processes. changes to the main thread may affect the
behavior of the other threads of the process.
Process Thread
A process does not share data with each
Threads share data with each other.
other.
Thread Life Cycle
thread in Python goes through different states during its execution. These states together
define the Thread Life Cycle.
Thread Life Cycle Stages
1. New (Created)
2. Runnable (Ready)
3. Running
4. Blocked (Waiting)
5. Terminated (Dead)
1. New (Created)
A thread is created using the Thread class but has not started execution yet.
At this stage, the thread object exists but is not yet scheduled for execution.
Example:
python
CopyEdit
import threading
def task():
print("Thread is running...")
# Creating a thread (New state)
t1 = [Link](target=task)
print("Thread Created but not started yet.")
2. Runnable (Ready)
The thread is ready to run but is waiting for the CPU to schedule it.
When start() is called, the thread moves to the Runnable state.
Example:
python
CopyEdit
[Link]() # Thread is now ready to run
print("Thread is in Runnable state.")
3. Running
The thread is now executing and performing its task.
The CPU has assigned time for this thread to run.
Example:
python
CopyEdit
def task():
print("Thread is running...")
t2 = [Link](target=task)
[Link]() # The thread moves to the Running state
4. Blocked (Waiting)
The thread is paused and waiting for some resource or event to resume execution.
It can be in this state due to:
o I/O operations (e.g., waiting for a file to be read)
o Sleeping (e.g., using [Link]())
o Lock acquisition (waiting for a resource to be released by another thread)
Example:
import time
def task():
print("Thread going to sleep...")
[Link](3) # Thread is blocked for 3 seconds
print("Thread resumed.")
t3 = [Link](target=task)
[Link]()
5. Terminated (Dead)
The thread has finished execution or has been stopped manually.
Once completed, the thread cannot be restarted.
Example:
[Link]() # Ensures thread execution completes before moving ahead
print("Thread has completed execution and is now Terminated.")
Example: Complete Example for Thread Life Cycle
import threading
import time
# Function to simulate a time-consuming task
def task():
print(f"{threading.current_thread().name} is in the running state.")
[Link](2)
print(f"{threading.current_thread().name} has completed its task and entered the
terminated state.")
# Create two thread objects
thread1 = [Link](target=task, name="Thread 1")
thread2 = [Link](target=task, name="Thread 2")
# Start the threads
[Link]()
[Link]()
# Wait for both threads to finish
[Link]()
[Link]()
print("Both threads have finished and are in the terminated state.")
In this example:
1. We import the threading module, which provides tools for working with threads.
2. We define a task function that simulates a time-consuming task by printing messages
and sleeping for 2 seconds.
3. We create two thread objects, thread1 and thread2, specifying the task function as the
target for both threads. We also assign names to the threads to identify them.
4. We start both threads using the start() method. This initiates their execution and
transitions them from the “new” state to the “runnable” state.
5. We use the join() method to wait for both threads to finish. This ensures that the main
thread does not proceed until both thread1 and thread2 have completed their tasks and
entered the “terminated” state.
6. Finally, we print a message indicating that both threads have finished.
Thread Synchronization
Thread synchronization in Python is essential when multiple threads are accessing shared
resources to prevent data corruption and ensure consistency. Python’s threading module
provides several synchronization mechanisms:
1. Lock
A Lock (or mutex) allows only one thread to access a critical section at a time.
import threading
lock = [Link]()
def critical_section():
with lock: # Acquires and releases the lock automatically
# Critical section (only one thread at a time)
print(f"Thread {threading.current_thread().name} is running")
# Creating multiple threads
threads = [[Link](target=critical_section) for _ in range(5)]
for t in threads:
[Link]()
for t in threads:
[Link]()
2. RLock (Reentrant Lock)
RLock (Reentrant Lock) allows a thread to acquire the same lock multiple times without getting
blocked.
rlock = [Link]()
def recursive_function(n):
if n <= 0:
return
with rlock:
print(f"Thread {threading.current_thread().name} acquired RLock")
recursive_function(n - 1)
t1 = [Link](target=recursive_function, args=(3,))
[Link]()
[Link]()
3. Semaphore
A Semaphore limits the number of threads that can access a resource.
semaphore = [Link](2) # Allows only 2 threads at a time
def limited_access():
with semaphore:
print(f"Thread {threading.current_thread().name} entered")
[Link]().wait(1) # Simulating some work
threads = [[Link](target=limited_access) for _ in range(5)]
for t in threads:
[Link]()
for t in threads:
[Link]()
4. Event
An Event is used for signaling between threads.
event = [Link]()
def wait_for_event():
print("Thread waiting for event to be set...")
[Link]() # Blocks until [Link]() is called
print("Event received!")
t = [Link](target=wait_for_event)
[Link]()
import time
[Link](2)
[Link]() # Unblocks the waiting thread
[Link]()
5. Condition
A Condition allows threads to wait until some condition is met.
condition = [Link]()
data_ready = False
def producer():
global data_ready
with condition:
print("Producing data...")
data_ready = True
[Link]() # Notify one waiting thread
def consumer():
with condition:
while not data_ready:
print("Waiting for data...")
[Link]() # Wait until notified
print("Consumed data!")
t1 = [Link](target=consumer)
t2 = [Link](target=producer)
[Link]()
[Link]()
[Link]()
[Link]()
What is Daemon thread
A daemon thread in Python (Or any programming language) is a type of thread that runs in the
background and does not prevent the program (or main thread) from exiting. In other words,
daemon thread does not block main thread to exiting and continue run in the background.
Types of Threads
Type Description Use Case
User Thread Runs independently, keeps program General tasks
alive
Daemon Thread Runs in the background, stops with Logging, monitoring
main program
CPU-Bound Uses high CPU, affected by GIL Not recommended, use
Thread multiprocessing
I/O-Bound Thread Handles waiting tasks efficiently Network requests, file I/O
Single-Threaded Runs tasks sequentially Simple programs
Multi-Threaded Runs multiple tasks concurrently Improves performance for
I/O-bound tasks