Overview:
- A race condition is a scenario in which, multiple threads compete for a resource. A resource could be a python variable, a file object and so on.
- When multiple threads access the same resource for example a global variable, it could lead to data inconsistencies as threads read / write order is not serialised.
- Here is a sample Python program that does not take care of race conditions.
- The final value of variable NumberOfVisits may not be consistent during each execution of the program as access to the variable NumberOfVisits is not serialized against race conditions that could arise from multiple threads.
Example 1 - Without Lock:
import threading import random import time
NumberOfVisits = 0 ThreadList = [] ThreadCount = 20
def AddVistor(): # Sleep for random seconds randomSeconds = random.random() time.sleep(randomSeconds)
#Increment Counter global NumberOfVisits SomeVar = NumberOfVisits SomeVar = SomeVar +1 NumberOfVisits = SomeVar
#Create several threads for index in range(ThreadCount): ThreadInstance = threading.Thread(target=AddVistor()) ThreadList.append(ThreadInstance) ThreadInstance.start()
for index in range(ThreadCount): ThreadList[index].join()
# print the final count print(NumberOfVisits)
|
Example 2 - With Lock:
- A lock object comes handy here. Any instance that has acquired a lock, makes its state not modifiable by concurrent threads. Once a python object acquires a lock by calling the acquire() method of the Lock instance, another thread can not modify the object state till the lock is released by calling the release() method.
- An example python program that effectively guards a counter variable against race conditions is given here.
import threading import random import time
class SafeCounter(object): def __init__(self): self.lock = threading.Lock() # Create a lock object. Initial state is unlocked state self.countValue = 0
def CounterIncrease(self): try: # Acquire a lock before modifying the object state self.lock.acquire()
# Increment the counter value self.countValue = self.countValue + 1 finally: # Release the lock in any case self.lock.release()
def SessionThread(CounterObject): # Increase the count after random duration for index in range(ThreadCount): randomSeconds = random.random() time.sleep(randomSeconds) CounterObject.CounterIncrease()
ThreadCount = 20
# Create many session threads CounterObject = SafeCounter() threading.Thread(target=SessionThread(CounterObject))
print(CounterObject.countValue)
|
- In the above python program SafeCounter acquires a lock on each increment of its counter variable countValue.
- The acquired lock is released after the counter is incremented. After the lock is released any other thread can access the countValue.
- While the lock is in acquired state, any other thread that accesses the countValue will be blocked.
- Access is provided only if a thread acquires a lock for which multiple threads may complete upon the release of the lock by the thread that is already holding the lock.