What are locks in Python?

Race condition is a significant problem in concurrent programming. The condition occurs when one thread tries to modify a shared resource at the same time that another thread is modifying that resource – t​his leads to garbled output, which is why threads need to be synchronized.

The threading module of Python includes locks as a synchronization tool. A lock has two states:

  • locked
  • unlocked

A lock can be locked using the acquire() method. Once a thread has acquired the lock, all subsequent attempts to acquire the lock are blocked until it is released. The lock can be released using the release() method.

Calling the release() method on a lock, in an unlocked state, results in an error.

1 of 3

Code

Example

The following code shows how locks can be used in Python with a simple example:

Suppose that there are 100 dollars in a bank account. Every month, 10 dollars are deposited as profit, and 10 dollars are deducted to pay a bill. thread1 deposits the profit and thread2 pays for the bill. In some months, the profit is deposited after the bill is paid. However, this should not affect the final amount in the account.

Race Condition

The final answer might be incorrect due to the race condition. There may be cases where a thread is unable to write the updated value to the shared variable deposit before the context switch, and the other thread reads a non-updated value; thus, leading to an unpredictable result.

You may have to run the code several times to re-create the error.

# Importing the threading module
import threading
deposit = 100
# Function to add profit to the deposit
def add_profit():
global deposit
for i in range(100000):
deposit = deposit + 10
# Function to deduct money from the deposit
def pay_bill():
global deposit
for i in range(100000):
deposit = deposit - 10
# Creating threads
thread1 = threading.Thread(target = add_profit, args = ())
thread2 = threading.Thread(target = pay_bill, args = ())
# Starting the threads
thread1.start()
thread2.start()
# Waiting for both the threads to finish executing
thread1.join()
thread2.join()
# Displaying the final value of the deposit
print(deposit)

Using a lock to solve the problem

The code between the acquire() and release() methods are executed atomically so that there is no chance that a thread will read a non-updated version after another thread has already made a change.

# Importing the threading module
import threading
# Declraing a lock
lock = threading.Lock()
deposit = 100
# Function to add profit to the deposit
def add_profit():
global deposit
for i in range(100000):
lock.acquire()
deposit = deposit + 10
lock.release()
# Function to deduct money from the deposit
def pay_bill():
global deposit
for i in range(100000):
lock.acquire()
deposit = deposit - 10
lock.release()
# Creating threads
thread1 = threading.Thread(target = add_profit, args = ())
thread2 = threading.Thread(target = pay_bill, args = ())
# Starting the threads
thread1.start()
thread2.start()
# Waiting for both the threads to finish executing
thread1.join()
thread2.join()
# Displaying the final value of the deposit
print(deposit)
Copyright ©2024 Educative, Inc. All rights reserved