This article originated from personal public account: TechFlow, original is not easy, for attention
Today, in the 24th installment of our Python series, we take a look at another indispensable part of the multithreaded scenario: locking.
If you’ve studied operating systems, you’re probably familiar with locking. A lock is a thread lock and can be used to specify that only one thread can access a logic or resource at a time. This is easy to understand. It’s like having a room locked with a lock that only the person who has the key can enter. Everyone gets the key from the door to enter the room and returns it to the door on their way out. So the next person at the door can get the key. The room is a resource or a piece of logic, and the person holding the key is actually a thread.
The reason for locking
Now that we understand the principle of locking, we can’t help but ask the question, why do we need locks and in what scenarios are they used?
In fact, it is used in a very wide range of scenarios, we take a very simple example, is taobao to buy things. We all know that the inventory of the merchant is limited, sell one for less. Let’s say there’s only one item left in stock, but two people are buying at the same time. Two people buying at the same time means that there are two requests initiating the purchase request at the same time. If we do not lock, both threads find the inventory of goods is 1, greater than 0, after the purchase logic, minus 1. Since both threads are executing at the same time, the inventory of the item ends up being -1.
Obviously the inventory of goods should not be a negative number, so we need to avoid this situation. Locking is the perfect way to solve this problem. We specify that only one thread can initiate a purchase request at a time, so that when one thread reduces the inventory to 0, the second request cannot be modified, thus ensuring the accuracy of the data.
Code implementation
So how do we implement this lock in Python?
It’s very simple. The Threading library already has tools for threading, so we can just grab them and use them. We can easily implement method locking using the Threading Lock object.
import threading
class PurchaseRequest:
' ' '
Initialize inventory and locks
' ' '
def __init__(self, initial_value = 0):
self._value = initial_value
self._lock = threading.Lock()
def incr(self,delta=1):
' ' '
Add the inventory
' ' '
self._lock.acquire()
self._value += delta
self._lock.release()
def decr(self,delta=1):
' ' '
Inventory reduction
' ' '
self._lock.acquire()
self._value -= delta
self._lock.release()
Copy the code
Acquire (); acquire(); acquire(); acquire(); The Lock object ensures that only one thread can acquire the Lock at a time, and that the Lock is acquired before execution continues. When we are done, we need to “put the lock back in the doorway”, so we need to call release again to indicate that the lock is released.
A small problem here is that many programmers always forget release when programming, resulting in unnecessary bugs, and bugs in such distributed scenarios are difficult to find through testing. Because it is often difficult to test concurrent scenarios during testing and easy to ignore during code review, it is still difficult to detect leaks.
To solve this problem, Lock also provides an improved use of the with statement. The with statement is something we’ve used before when we were using files, and it allows us to do things like try catch and recycle, and we’re done with it. Here, too, we can write the code without having to apply or release the lock, so we can rewrite the code like this:
import threading
class PurchaseRequest:
' ' '
Initialize inventory and locks
' ' '
def __init__(self, initial_value = 0):
self._value = initial_value
self._lock = threading.Lock()
def incr(self,delta=1):
' ' '
Add the inventory
' ' '
with self._lock:
self._value += delta
def decr(self,delta=1):
' ' '
Inventory reduction
' ' '
with self._lock:
self._value -= delta
Copy the code
Doesn’t it look cleaner?
Reentrant lock
These are the simplest locks, but the ones we use most often are reentrant locks.
What is a reentrant lock? To put it simply, a thread can re-enter the locked region if it already holds the lock. But since the thread still holds the lock, shouldn’t it still be in the locked region? How can it need to enter the locked region again? In fact, there is, the reason is very simple, is recursion.
If we change the above example a little bit, it will be completely different.
import threading
class PurchaseRequest:
' ' '
Initialize inventory and locks
' ' '
def __init__(self, initial_value = 0):
self._value = initial_value
self._lock = threading.Lock()
def incr(self,delta=1):
' ' '
Add the inventory
' ' '
with self._lock:
self._value += delta
def decr(self,delta=1):
' ' '
Inventory reduction
' ' '
with self._lock:
self.incr(-delta)
Copy the code
Looking at the decR method above, we implemented decR using INCR instead of the original logic. One problem is that decR is also a lock method, which requires the previous lock to be released in order to enter. But it already holds the lock, so a deadlock occurs in this case.
We can solve this problem simply by replacing the Lock with a reentrant Lock, with a single line of code change.
import threading
class PurchaseRequest:
' ' '
Initialize inventory and locks
We use RLock instead of Lock, and reentrant locks instead of regular locks
' ' '
def __init__(self, initial_value = 0):
self._value = initial_value
self._lock = threading.RLock()
def incr(self,delta=1):
' ' '
Add the inventory
' ' '
with self._lock:
self._value += delta
def decr(self,delta=1):
' ' '
Inventory reduction
' ' '
with self._lock:
self.incr(-delta)
Copy the code
conclusion
Today’s article introduces the use of locks in Python and the concept of reentrant locks. Developing and debugging in a concurrent scenario is a difficult task. If you are not careful, you can step into a variety of pits. Deadlock is just one of the more common and easy to solve problems, but there are many other problems.
Python also provides other solutions to the deadlock problem, which we’ll share in the next article.
This is the end of today’s article, if you like this article, please invite a wave of quality sanlian, give me a little support (follow, forward, like).
– END –