aiorwlock: Read Write Lock for Asyncio
aiorwlock provides a read-write lock, a synchronization primitive for `asyncio` applications. It allows multiple reader tasks to hold a lock concurrently, while a writer task obtains an exclusive lock, blocking all readers and other writers. This pattern is ideal for resources that are frequently read but infrequently written. The library is currently at version 1.5.1 and maintains an active release cadence with several minor and patch updates per year.
Warnings
- breaking Python 3.7 support was dropped in v1.4.0. Users on Python 3.7 or older must upgrade their Python version to use `aiorwlock` v1.4.0 or newer.
- breaking The explicit `loop` parameter for the `RWLock` constructor was deprecated in v1.0.0 and removed in v1.3.0. Passing a `loop` argument will now raise an error.
- deprecated Creation of `RWLock` instances outside of an `async` function context was deprecated in v1.0.0. While it might still work in some setups due to lazy loop evaluation, it's considered bad practice.
- gotcha Fixed a cross-event-loop race condition in lock acquisition and a deadlock that could occur when tasks are cancelled.
- gotcha Fixed a bug that allowed concurrent writes under rare conditions.
- gotcha A `RuntimeError` will be raised if a lock is acquired by one task but released by another. Synchronization primitives in `asyncio` are typically task-bound.
Install
-
pip install aiorwlock
Imports
- RWLock
from aiorwlock import RWLock
Quickstart
import asyncio
from aiorwlock import RWLock
async def reader_task(rwlock: RWLock, reader_id: int):
print(f"Reader {reader_id}: Attempting to acquire read lock")
async with rwlock.reader_lock:
print(f"Reader {reader_id}: Acquired read lock")
await asyncio.sleep(0.05) # Simulate reading
print(f"Reader {reader_id}: Released read lock")
async def writer_task(rwlock: RWLock, writer_id: int):
print(f"Writer {writer_id}: Attempting to acquire write lock")
async with rwlock.writer_lock:
print(f"Writer {writer_id}: Acquired write lock")
await asyncio.sleep(0.1) # Simulate writing
print(f"Writer {writer_id}: Released write lock")
async def main():
rwlock = RWLock()
tasks = []
# Start some readers
for i in range(3):
tasks.append(asyncio.create_task(reader_task(rwlock, i)))
# Start a writer in between
tasks.append(asyncio.create_task(writer_task(rwlock, 100)))
# Start more readers
for i in range(3, 6):
tasks.append(asyncio.create_task(reader_task(rwlock, i)))
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())