Credit: Sami Hangaslammi
You need to allow unlimited read access to a resource when it is not being modified while keeping write access exclusive.
“One-writer, many-readers” locks are a frequent necessity, and Python does not supply them directly. As usual, they’re not hard to program yourself, in terms of other synchronization primitives that Python does supply:
import threading class ReadWriteLock: """ A lock object that allows many simultaneous "read locks", but only one "write lock." """ def _ _init_ _(self): self._read_ready = threading.Condition(threading.Lock( )) self._readers = 0 def acquire_read(self): """ Acquire a read lock. Blocks only if a thread has acquired the write lock. """ self._read_ready.acquire( ) try: self._readers += 1 finally: self._read_ready.release( ) def release_read(self): """ Release a read lock. """ self._read_ready.acquire( ) try: self._readers -= 1 if not self._readers: self._read_ready.notifyAll( ) finally: self._read_ready.release( ) def acquire_write(self): """ Acquire a write lock. Blocks until there are no acquired read or write locks. """ self._read_ready.acquire( ) while self._readers > 0: self._read_ready.wait( ) def release_write(self): """ Release a write lock. """ self._read_ready.release( )
It is often convenient to allow unlimited read access to a resource
when it is not being modified and still keep write access exclusive.
While the threading
module does not contain a
specific class for the job, the idiom is easy to implement using a
Condition
object, and this recipe shows how you
can do that.
An instance of the
ReadWriteLock
class is initialized without arguments,
as in:
rw = ReadWriteLock( )
Internally, rw._readers
counts the number of
readers who are currently in the read-write lock (initially zero).
The actual synchronization is performed by a
threading.Condition
object (created at _ _init_ _
around a new Lock
object and held in
rw._read_ready
).
The
acquire_read
and
release_read
methods increment and decrement the number of active readers. Of
course, this happens between acquire
and
release
calls to
_read_ready
—such
bracketing is obviously necessary even to avoid race conditions
between different threads wanting to acquire or release a read lock.
But we also exploit _read_ready
for another
purpose, which is why release_read
also does a
notifyAll
on it, if and when it notices it has
removed the last read lock.
The notifyAll
method of a
Condition
object wakes up all threads (if any)
that are on a wait
condition on the object. In
this recipe, the only way a thread can get into such a wait is via
the acquire_write
method, when it finds there are
readers active after acquiring _read_ready
. The
wait
call on the Condition
object releases the underlying lock, so
release_read
methods can execute, but reacquires
it again before waking up, so acquire_write
can
safely keep checking whenever it wakes up, if it’s
finally in a no-readers-active situation. When that happens,
acquire_write
returns to its caller, but keeps the
lock, so no other writer or reader can enter again, until the writer
calls release_write
, which lets the lock go again.
Note that this recipe offers no guarantee against what is technically known as a starvation situation. In other words, there is no guarantee that a writer won’t be kept waiting indefinitely by a steady stream of readers arriving, even if no reader keeps its read lock for very long. If this is a problem in your specific application, you can avoid starvation by adding complications to ensure that new readers don’t enter their lock if they notice that a writer is waiting. However, in many cases, you can count on situations in which no readers are holding read locks, without special precautions to ensure that such situations occur. In such cases, this recipe is directly applicable, and besides eschewing complications, it avoids potentially penalizing reader performance by making several readers wait for one pending writer.
Get Python Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.