set in motion
Python's multithreading model does not provide read/write locks. Read/write locks are more applicable than simple mutex locks, which can be used by more than one thread at the same time in read mode, but can only be used by one thread in write mode.
In layman's terms, this means that when there is no write lock, you can add a read lock and any thread can be added at the same time; whereas a write lock can only have one thread, and must be added when there is no read lock.
simple implementation
import threading class RWlock(object): def __init__(self): self._lock = () self._extra = () self.read_num = 0 def read_acquire(self): with self._extra: self.read_num += 1 if self.read_num == 1: self._lock.acquire() def read_release(self): with self._extra: self.read_num -= 1 if self.read_num == 0: self._lock.release() def write_acquire(self): self._lock.acquire() def write_release(self): self._lock.release()
This is a simple implementation of a read/write lock. self.read_num is used to hold the number of threads that have acquired the read lock, and this attribute belongs to the critical zone, which is also subject to locking, so an additional lock, self._extra, is needed here to protect the internal data.
But this lock is unfair. Ideally, threads should have the same chance of acquiring the lock, regardless of whether the thread is reading or writing. As you can see from the code above, a read request sets self.read_num += 1 immediately, regardless of whether the lock is acquired or not, while a write request has to wait for read_num to be 0 in order to acquire the lock.
So this one creates a situation where write access can be gained only if the lock is not occupied or if there are no read requests. We should find a way to avoid read mode locks being occupied for a long time.
Priority of read and write locks
There are also read-first and write-first locks. The above code is read-first.
If you want to change to write-first, then switch to record the reference count of the write thread, read and write at the same time when the competition, you can let the write thread to increase the write count, so that the read thread can make the read thread read the lock has not been obtained, because the read thread to determine the reference count of the write, if not 0, then wait for it to be 0, and then read. This part of the code is not listed.
But this is clearly not flexible enough. We don't need two similar read/write lock classes. We want to refactor our code to make it more powerful.
revised
In order to be able to satisfy a read/write lock with a customized priority, the number of waiting read/write threads should be kept track of, and two conditions should be used to handle the notification of which side has priority. The count reference can be extended with semantics: positive number: indicates the number of threads in a read operation, negative number: indicates the number of threads in a write operation (up to -1)
In the acquisition of the read operation, first and then determine when there is a waiting write thread, there is no, read the operation, there is, then waiting to read the count plus 1 after waiting for Condition notification; waiting to read the count minus 1, the count references plus 1, continue to read the operation, if the conditions do not hold, the cycle of waiting;
When acquiring a write operation, if the lock is not occupied, the reference count is decremented by 1. If it is occupied, the number of threads waiting to write is increased by 1, and the notification of the write condition Condition is waited for.
The release is the same for both read and write modes, and the corresponding Condition needs to be notified based on the judgment:
class RWLock(object): def __init__(self): = () = () = () self.read_waiter = 0 # of threads waiting to acquire a read lock self.write_waiter = 0 # of threads waiting to acquire a write lock = 0 # Positive number: indicates number of threads in read operation Negative number: indicates number of threads in write operation (up to -1) = [] # Collection of thread ids being manipulated self.write_first = True # Write-first by default, False for read-first def write_acquire(self, blocking=True): # Acquire a write lock only if me = threading.get_ident() with : while not self._write_acquire(me): if not blocking: return False self.write_waiter += 1 () self.write_waiter -= 1 return True def _write_acquire(self, me): # Acquire a write lock only if the lock is unoccupied, or if the current thread is already occupied. if == 0 or ( < 0 and me in ): -= 1 (me) return True if > 0 and me in : raise RuntimeError('cannot recursively wrlock a rdlocked lock') return False def read_acquire(self, blocking=True): me = threading.get_ident() with : while not self._read_acquire(me): if not blocking: return False self.read_waiter += 1 () self.read_waiter -= 1 return True def _read_acquire(self, me): if < 0: # If the lock is occupied by a write lock return False if not self.write_waiter: ok = True else: ok = me in if ok or not self.write_first: += 1 (me) return True return False def unlock(self): me = threading.get_ident() with : try: (me) except ValueError: raise RuntimeError('cannot release un-acquired lock') if > 0: -= 1 else: += 1 if not : if self.write_waiter and self.write_first: # If a write operation is pending (default write priority) () elif self.read_waiter: .notify_all() elif self.write_waiter: () read_release = unlock write_release = unlock
This is the whole content of this article.