nobodd.locks
This module implements an SREW lock with
re-entrancy and optional non-blocking behaviours. This is used in the
FatFileSystem and FatPath
implementations to safely manage multi-threaded access to the underlying
file-system.
Classes
- class nobodd.locks.RWLock[source]
Synchronization object used in a solution of so-called second readers-writers problem.
In this problem, many readers can simultaneously access a share, and a writer has an exclusive access to this share. Additionally, the following constraints should be met:
no reader should be kept waiting if the share is currently opened for reading unless a writer is also waiting for the share,
no writer should be kept waiting for the share longer than absolutely necessary.
The implementation is based on [1], secs. 4.2.2, 4.2.6 with modifications for a more “Pythonic” style, and re-entrancy. The class provides two objects,
readandwritewhich each support the context manager protocol along with the typicalacquireandreleasemethods.Unlike the implementation in the book, both
readandwriteare re-entrant (in the book version,readis re-entrant by virtue of it being shared, butwriteis not). Furthermore, upgrade is permitted from read lock to write lock, provided all locks are subsequently released in the order they were obtained. In other words, a thread may obtain a read lock, then implicitly “upgrade” to a write lock, provided it subsequently releases the write lock, then the original read lock.- read
The sub-object providing access to read locks. This object supports the context manager protocol, and the typical
acquireandreleasemethods (seeLock.acquire()andLock.release()).The lock obtained via this object is re-entrant and shared (many threads may simultaneously share the read lock).
- write
The sub-object providing access to write locks. This object supports the context manager protocol, and the typical
acquireandreleasemethods (seeLock.acquire()andLock.release()).The lock obtained via this object is re-entrant but exclusive (no other thread may hold a read or write lock while this is held). A thread may upgrade from a read lock to a write lock, but the lock is not held continuously during upgrade: the read lock is released before the upgraded write lock is obtained. Bear in mind another write lock may be obtained by another thread before this thread obtains the upgraded write lock.
- class nobodd.locks.RWLockState[source]
State of a thread in
RWLock. This tracks the number of re-entrant calls a thread has made.Consider a hypothetical thread performing a series of operations. The values that its RWLockState move through will be as follows:
Operation
read
write
ignored
Notes
0
0
0
start
read.acquire
1
0
0
read.acquire
2
0
0
read.acquire
3
0
0
write.acquire
3
1
0
upgrade
write.acquire
3
2
0
read.acquire
3
2
1
ignore
read.acquire
3
2
2
read.release
3
2
1
read.release
3
2
0
write.release
3
1
0
write.release
3
0
0
downgrade
read.release
2
0
0
read.release
1
0
0
read.release
0
0
0
- upgrade
At this point this thread holds three re-entrant acquisitions of the read lock, but has requested a write lock (effectively requesting to upgrade its lock). What this does is release all the read locks held (actually one lightswitch release), and start a write lock acquisition
- ignore
If a thread holds any write locks (whether through upgrade, or because it originally grabbed a write lock) all attempted acquisitions of a read lock are ignored (but counted)
- downgrade
Here a thread has released its last write lock while in an upgraded state. The implementation will release the write lock then re-acquire the number of read-locks specified (actually one lightswitch acquisition)
- class nobodd.locks.LightSwitch(lock)[source]
An auxiliary “light switch”-like object. The first thread to acquire the switch also acquires the lock associated with the switch. The last thread to release the switch also releases the lock associated with the switch.
If the first and last threads can differ, you must use a primitive
threading.Lockorthreading.Semaphore(initialized to 1) as the value of the lock parameter. If the first and last threads will always be the same, you may use a re-entrantthreading.RLock.This implementation is based on [1], sec. 4.2.2 but with additions to permit non-blocking execution or timeouts in blocking mode, and operation as a context manager.
Support functions
- nobodd.locks.remaining(timeout, start)[source]
Calculate the amount of timeout remaining if the routine started at start (measured by
time.monotonic()).If timeout is -1 (meaning infinite timeout), this function always returns -1. Otherwise, the time remaining is calculated and clamped to 0.