Why Lock() ?
- When 2 or more operations belonging to concurrent threads try to access the shared memory, a race condition can occur
- The easiest way to get around the race conditions is the use of a lock
- The operation of a lock is simple when a thread wants to access a portion of shared memory, it must necessarily acquire a lock on that portion prior to using it.
- After completing its operation, the thread must release the lock that was previously obtained.
- The impossibility of incurring races is critical as the need of the lock for the thread.
Deadlock
- A deadlock occurs due to the acquisition of a lock from different threads
- It is impossible to proceed with the execution of operations
Advantages of Lock()
- It allows us to restrict the access of a shared resource to a single thread or a single type of thread at a time
- Before accessing the shared resource of the program, the thread must acquire the lock and must then allow any other threads access to the same resource.
import threading
shared_resource_with_lock = 0
shared_resource_with_no_lock = 0
COUNT = 10000
shared_resource_lock = threading.Lock()
# Lock Management
def increment_with_lock():
global shared_resource_with_lock
for i in range(COUNT):
shared_resource_lock.acquire()
shared_resource_with_lock += 1
shared_resource_lock.release()
def decrement_with_lock():
global shared_resource_with_lock
for i in range(COUNT):
shared_resource_lock.acquire()
shared_resource_with_lock -= 1
shared_resource_lock.release()
def increment_without_lock():
global shared_resource_with_no_lock
for i in range(COUNT):
shared_resource_with_no_lock += 1
def decrement_without_lock():
global shared_resource_with_no_lock
for i in range(COUNT):
shared_resource_with_no_lock -= 1
if __name__ == "__main__":
t1 = threading.Thread(target = increment_with_lock)
t2 = threading.Thread(target = decrement_with_lock)
t3 = threading.Thread(target = increment_without_lock)
t4 = threading.Thread(target = decrement_without_lock)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print("the value of shared variable with lock management is %s"\
%shared_resource_with_lock
)
print("the value of shared variable with race condition is %s" \
%shared_resource_with_no_lock)
- Locks have two states: Locked and unlocked
- We have two methods that are used to manipulate the locks: acquire() and release()
Rules
- If the state is unlocked, a call to acquire() changes state to locked
- If the state is locked, a call to acquire() blocks until another thread calls release()
- If the state is unlocked, a call to release() raises a Runtime Error exception
- If the state is locked, a call to release() changes the state to unlocked
Disadvantages
- The locks are subject to harmful situations of deadlock
- They have many other negative aspects for the application as a whole
- It also limits the scalability of the code and its readability
- The use of a lock is in conflict with the possible need to impose the priority of access to the memory shared by the various processes
- An application containing a lock presents considerable difficulties when searching for errors or debugging