Embedding locks in the data structure of the counter

The goal of a good lock-based concurrent data structure is to have its locks internally implemented within its class attributes and methods, so that external functions and programs cannot bypass those locks and access a shared concurrent object simultaneously. For our counter data structure, we will be adding an additional attribute for the class, which will hold the lock object that corresponds to the value of the counter. Consider the following new implementation of the data structure in the Chapter16/example2.py file:

# Chapter16/example2.py

import threading
import time

class LockedCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()

def increment(self, x):
with self.lock:
new_value = self.value + x
time.sleep(0.001) # creating a delay
self.value = new_value

def get_value(self):
with self.lock:
value = self.value

return value

In this implementation of our counter data structure, a lock object is also initialized as an attribute of a LockedCounter instance, when that instance is initialized. Additionally, any time the value of the counter is accessed by a thread, whether for reading (the get_value() method) or updating (the increment() method), that lock attribute has to be acquired, to ensure that no other thread is also accessing it. This is done by using a context manager with the lock attribute.

Theoretically, this implementation should solve the problem of the race condition for us. In our main program, we are implementing the same thread pool that was used in the previous example. A shared counter will be created, and it will be incremented 300 times (each time by one unit), across three different threads:

# Chapter16/example2.py

from concurrent.futures import ThreadPoolExecutor

counter = LockedCounter()
with ThreadPoolExecutor(max_workers=3) as executor:
executor.map(counter.increment, [1 for i in range(300)])

print(f'Final counter: {counter.get_value()}.')
print('Finished.')

Run the script, and the output produced by the program should be similar to the following:

> python3 example2.py
Final counter: 300.
Finished.

As you can see, the problem of the race condition has been addressed successfully: the final value of the counter is 300, which corresponds perfectly to the number of increments that were executed. Furthermore, no matter how many times the program is run again, the value of the counter will always remain 300. What we currently have is a working, correct data structure for concurrent counters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.30.62