Package: fair-async-rlock
FairAsyncRLockMain
Status:Develop
Status:This is a well-tested standalone implementation of a fair reentrant lock for conncurrent programming with asyncio.
This was built
becausepython decided not to support RLock in asyncio,
theirargumentbeing that every
extra bit of functionality adds to maintenance cost.Install withpipinstallfair-async-rlockAbout Fair Reentrant Lock for AsyncIOA reentrant lock (or recursive lock) is a particular type of lock that can be "locked" multiple times by the same task
without causing a deadlock. This is in contrast to a standard lock (sometimes called a "non-reentrant lock"), which
could cause a deadlock if a task attempts to lock it multiple times. A "Fair" Reentrant Lock respects the order of lock
granting, so that all lock acquirers get acquisition in the order they asked for it.A reentrant lock can be useful in a number of scenarios:Recursive function calls: If you have a function that can be called recursively, and that function needs to lock a
resource, a reentrant lock would be useful. This is because the same task will be able to acquire the lock multiple
times, once for each recursive call.Complex operations: If you're performing a complex operation that consists of multiple steps, and each of these
steps
needs to lock a resource, a reentrant lock might be necessary. This is especially true if the steps are performed by
the
same task and if the steps can overlap in some way.Improve readability: Reentrant locks can sometimes make code easier to understand and debug. This is because you
don't
need to worry about a task deadlocking itself by attempting to acquire a lock it already holds.Task ownership: If your code logic depends on which task owns a lock, reentrant locks can be
helpful. They inherently track ownership since they are tied to the task that acquired them first.This code demonstrates the reentrant property:fromfair_async_rlockimportFairAsyncRLockasyncdefreentrant():lock=FairAsyncRLock()asyncwithlock:asyncwithlock:# This will not block, whereas it would with asyncio.LockassertTrueIn the context of Python's asyncio, FairAsyncRLock can be beneficial when used with
coroutines that might call each other or be invoked recursively. They also help when a single coroutine holds a lock and
wants to ensure fairness among other coroutines waiting for the same lock, so that no coroutine is left waiting for the
lock indefinitely while others after it acquire the lock, which can happen with standard asyncio Locks.This code demonstrates fairness:importasynciofromfair_async_rlockimportFairAsyncRLockasyncdeffairness():lock=FairAsyncRLock()num_tasks=1000order=[]asyncdefworker(n):asyncwithlock:order.append(n)awaitasyncio.sleep(0)# emulate work# Start several tasks to acquire the locktasks=[asyncio.create_task(worker(i))foriinrange(num_tasks)]# Make sure they all start and try to acquire the lock before releasing itawaitasyncio.sleep(0)awaitasyncio.gather(*tasks)assertorder==list(range(num_tasks))# The tasks have run in orderPotential downsidesIt's also important to note the potential downsides of using a reentrant lock.
For one, they can hide bugs in code that isn't properly synchronized, since
the same task can acquire a reentrant lock multiple times without issue.Non-reentrant locks are often simpler and can expose synchronization bugs more easily, so you should only use a
reentrant lock if you have a specific need for it.PerformanceThe performance is about 50% slower thanasyncio.Lock, i.e. overhead of sequential locks is about 3/2 of same
withasyncio.Lock.Change Log27 Jan, 2024 - 1.0.7 released. Fixed a bug that allowed another task to get the lock before a waiter got its turn on the
event loop.