Multithreading in Python | Threads & Process | Dead-Lock & Race Condition | Lock Variable
Processes and Threads:
A 'Process' is a program in execution, whereas a 'Thread' is a separate execution path in a 'Process'. A 'Thread' can be called as a 'Light-Weight Process' as all the threads within the process share the same computing resources like memory, processing power, etc.
Examples :
- Google Chrome -> 'Source Code + Deployment Set-Up' is the program in this case, 'Running application' is the process and different tabs can be considered as threads.
- MS Word -> Here, the different operations like 'Adding text', 'Adding Media', 'Inserting Links', etc can be considered as Threads within the process(MS Word).
Multithreading:
- It is the mechanism of running/executing multiple threads within the same process.
- Module in Python: threading
- 'threading' is already available in the installed python -versions. Just import the module and you are good to go!
Tutorial:
# Import the threading module to work with threads
import threading
# Define a function that will be executed by each thread
def test(x):
print("Hello", x)
# Create a list of threads, each targeting the 'test' function with different arguments
threads = [threading.Thread(target=test, args=(i,)) for i in ['a', 'b', 'c']]
# Start each thread in the list
for i in threads:
i.start()
Output:
Hello a
Hello b
Hello c
Mutual Exclusion - Lock (or) Mutex Variable:
# Import the required modules
import time # Import the time module for sleep function
import threading # Import the threading module for creating and managing threads
# Define a function to be run in the threads
def test1(x):
for i in range(3):
print("{} iteration for arg {}".format(i, x))
time.sleep(1) # Sleep for 1 second to simulate some work
# Create a list of threads
threads = [threading.Thread(target=test1, args=(i,)) for i in range(1, 4)]
# Start each thread
for thread in threads:
thread.start()
Output:
0 iteration for arg 1
0 iteration for arg 2
0 iteration for arg 3
1 iteration for arg 1
1 iteration for arg 2
1 iteration for arg 3
2 iteration for arg 1
2 iteration for arg 2
2 iteration for arg 3
In this case, the first running thread sleeps for 1 second after the print function in the 1st iteration, then the second thread executes its 1st iteration and sleeps, and the same continues. This occurs due to the fact that all the threads within the same process share the same resources and whenever a thread sleeps the other comes into play.
To avoid this we use Lock-Variable. By using this we achieve Mutual-Exclusion.
import time # Import the time module for sleep function
import threading # Import the threading module for creating and managing threads
# Define a function to run in the threads
def test1(x):
for i in range(3):
print("{} iteration for arg {}".format(i, x))
time.sleep(1) # Sleep for 1 second to simulate some work
# Create a list of threads
threads = [threading.Thread(target=test1, args=(i,)) for i in range(1, 4)]
# Create a thread lock to synchronize thread execution
lock = threading.Lock()
# Start each thread while holding the lock to prevent interleaving
for thread in threads:
with lock: # Acquire the lock before starting the thread
thread.start()
In this code, a thread lock is introduced to address the issue of output interleaving that was present in the previous code. The lock ensures that only one thread can execute its code within the context of the lock at a time.
This approach is better than the previous code because it adds synchronization. Without synchronization, the threads could interleave their output messages and cause confusion. By using a thread lock, the "start" method of each thread is wrapped in a lock context, ensuring that each thread starts execution one after the other, without overlapping output. This results in a clearer and more organized output that reflects the intended behaviour.
Output:
0 iteration for arg 1
1 iteration for arg 1
2 iteration for arg 1
0 iteration for arg 2
1 iteration for arg 2
2 iteration for arg 2
0 iteration for arg 3
1 iteration for arg 3
2 iteration for arg 3
Dead-Lock & Race Condition:
Dead-Lock : It is a condition when a process A having access to resource i and waiting for access to resource j is in loop with a process B which has the access locked to resource j and waiting for resource i.
Race Condition : It is a condition that appears when two or more threads sharing access to the resources and variables proceeds to their executions according to their schedule but it might lead to overwriting of variables leading to unexpected outcomes.
Pros & Cons of Multithreading:
Pros:
- Multiple tasks can be done simultaneously.
- Resource Sharing.
- Efficient utilization of memory and processors.
- Overall performance improvements.
Cons:
- Synchronization is complex.
- Debugging is difficult.
- Memory and resources issues like deadlock, etc.
Some Useful Functions from this module:
- activeCount() : Returns the number of active threads.
- currentThread() : Returns the currently running thread.
- enumerate() : Returns a list of threads(active and dummy).
- run() : It is the entry point for the thread of a process.
- start() : It starts the execution of the thread.
- join() : It waits for the complete execution of the thread before moving to the execution of the next instruction in the main program.
- isAlive() : It checks if the thread is alive / running or completed.
Resources : Official Documentation
Comments in the code blocks are added using ChatGPT.
Do Upvote, Follow and Subscribe if you've learnt something from this post!
Linkedin: