Python — Threading In Python3 (3)

Race Condition Recap
So in my last article, we introduce “Race Condition” in Python3 and how to create a “Race Condition” situation. You can review it at: Python — Threading In Python3 (2)
How To Solve Race Condition?
There are few different ways of solving race condition. But the idea behind all of them is to allow only one thread modify shared the resource at a time. We will focus on Lock
in this article.
Lock
The idea here is to implement a “lock” to the resources and to allow only one thread at time into the read/update section of the code. In Python3 threading, the basic functions to do this is .acquire()
and .release()
. .acquire()
is called to get the lock and if the lock is already being given, the requiring thread will wait until it is released. In some situation, this could result a deadlock
situation, if one thread for some reason never returns the lock, your thread will be stuck. To avoid deadlock
, always use the with
context manager.
Now back to our program, let’s remove the comments and enable the lock:
import concurrent.futures
import threading
import time
class ShoppingCart:
def __init__(self):
self.items = 0
self._lock = threading.Lock()
def update(self, name):
print(f'Customer-{name}: starting update')
with self._lock:
print(f'Customer-{name} has lock')
local_cart = self.items
local_cart += 1
time.sleep(1)
self.items = local_cart
print(f'Customer-{name} releases lock')
print(f'Customer-{name}: finishing update')
def main():
print(f'Main: Before starting shopping')
shopping_cart = ShoppingCart()
print(f'Start shopping, the cart is {shopping_cart.items}')
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as customer:
customer.map(shopping_cart.update, range(2))
print(f'Finish shopping, the cart is {shopping_cart.items}')
print(f'Main: After finishing shopping')
if __name__ == '__main__':
main()
Run it:
Main: Before starting shopping
Start shopping, the cart is 0
Customer-0: starting update
Customer-0 has lock
Customer-1: starting update
Customer-0 releases lock
Customer-0: finishing update
Customer-1 has lock
Customer-1 releases lock
Customer-1: finishing update
Finish shopping, the cart is 2
Main: After finishing shopping
Since we use the with
context manager: with self._lock
, the lock will get initialized in unlocked state and locked/released automatically. Use context manager is always recommended, to avoid deadlock
Deadlock
Just for demonstration purpose, you can generate a deadlock
by using the following code snippet:
import threading
# Initialize a lock
t1 = threading.Lock()
print("Before the first lock acquire")
t1.acquire()
print("Before the second lock acquire")
t1.acquire()
print("Acquired lock twice")
Statement print("Acquired lock twice”)
will never print.