Lock - Synchronization Primitives In Python

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.

 


Copyright 2023 © pythontic.com