Introduction
Threads can be considered lightweight processes. Multithreading requires running two or more threads simultaneously to improve the system's performance. Thus, in multithreading, multiple threads execute concurrently. This helps our computer to perform various tasks at the same time.
Imagine a scenario where multiple threads have to modify/use some particular program section. For example, multiple threads want to write to the same file. One thread starts writing to a file, and simultaneously, another thread also starts writing to the same file. This will corrupt the data and ruin the entire purpose of the program.
Thus, we have seen that we can’t achieve total parallelism, and we need thread synchronization to coordinate between multiple threads. Thread Synchronization is a process to ensure that only one thread uses a critical resource at a time.
Methods of thread synchronization
There are various methods to handle thread synchronization. These methods can be divided into four categories:
- Blocking methods
- Locking constructs
- No blocking synchronization
- Signaling
Blocking Methods
These are the methods where one thread waits for another to finish execution for some time. Here, the execution of one process is blocked, hence the name blocking method. The following are some of the blocking methods:
- Sleep: It stops the execution of one thread for a defined time. For example - thread.sleep(100).
- Join: In join, we do not have to pre-define the time of blocking. It pauses the calling thread till the joined threads have completed execution. For example - thread.join().
- Wait: It allows the calling thread to stop executing until the current thread has completed execution. Example: thread.wait().
Locking Methods
Locking is a method of thread synchronization that limits the access of a resource in multiple threads. It allows only one thread at a time to enter the locked area. The following are some of the locking methods:
- Lock: A lock is applied before starting the critical section of the code. When one thread enters the critical section, it acquires the lock. Now no other thread can enter this section. Once the first thread has finished executing the critical section, it releases the lock, and the following thread can now enter this section.
- Mutex: Mutual Exclusion (Mutex) ensures that a particular section of code is executed only once at one time.
Example
Try to guess the output of the below python code:
import threading
# Function to print numbers from 0 to N
def fun(n):
for i in range (n):
print("Current value of i: ", i)
lock = threading.Lock()
# Creating two threads and calling fun with argument as 5
t1 = threading.Thread(target = fun, args=(5, ))
t2 = threading.Thread(target = fun, args=(5, ))
# Starting both threads
t1.start()
t2.start()
# Joining both threads
t1.join()
t2.join()
Output:
Current value of i: Current value of i: 00
Current value of i: Current value of i: 1
1Current value of i:
Current value of i: 2
2Current value of i:
Current value of i: 3 3
Current value of i: Current value of i: 44
Explanation: We can see that the output is completely messed up. This has happened because both threads used the function ‘fun’ simultaneously. And since we were printing inside the function, the print statements of both the threads were executed simultaneously and messed up the output.
Now try to guess the output of the following python code:
import threading
# Function to print numbers from 0 to N
def fun(n):
lock.acquire()
for i in range (n):
print("Current value of i: ", i)
lock.release()
lock = threading.Lock()
# Creating two threads and calling fun with argument as 10
t1 = threading.Thread(target = fun, args=(5, ))
t2 = threading.Thread(target = fun, args=(5, ))
# Starting both threads
t1.start()
t2.start()
# Joining both threads
t1.join()
t2.join()
Output:
Current value of i: 0
Current value of i: 1
Current value of i: 2
Current value of i: 3
Current value of i: 4
Current value of i: 0
Current value of i: 1
Current value of i: 2
Current value of i: 3
Current value of i: 4
Explanation: This time, we have used lock before the for loop. When one thread enters this area, it will acquire the lock and execute the for loop. The other thread will have to wait until the lock is released. Thus, we get a clean output.
Must Read: Multithreading in C#, Multithreading in Python