Home > Mobile >  Asyncio Loop and Lock
Asyncio Loop and Lock

Time:03-15

i was tinkering around with asyncio loops and locks and found something that I thought was strange

import asyncio
import time
lock = asyncio.Lock()

async def side_func():
    print("trying to acquire Side")
    await lock.acquire()
    print("Acquired side")
    lock.release()

async def func2():
    print("Trying to aacquire 2nd")
    await lock.acquire()
    print("Acquired second")
    lock.release()
    
async def func1():
    print("Acquiring 1st")
    await lock.acquire()
    await asyncio.sleep(2)
    print("Acquired 1st")
    lock.release()
    await side_func()


async def main():
    await asyncio.wait([func1(), func2()])
    
asyncio.run(main())

given this code runes func1 first, it should first be able to acquire the lock for func1, jump to func2 on the asyncio.sleep(2) and wait for the lock there, when the sleep finishes it should execute side func.

But when trying to acquire the lock for side_func, the event loop instead jumps to func2 and acquires the lock there. From my understanding, it should acquire the lock in side_func first and then be able to jump to other functions

For some reason it is jumping to the abandoned func2 function first, is this a behaviour globally applicable to asynchronous functions or is this specific to lock.acquire? I guess it would make sense for event loops to jump to abandoned yet completed states first but I'm not really sure if this globally applicable to async functions

CodePudding user response:

The internal behavior of the lock is this code :

    def release(self):
    """Release a lock.

    When the lock is locked, reset it to unlocked, and return.
    If any other coroutines are blocked waiting for the lock to become
    unlocked, allow exactly one of them to proceed.

    When invoked on an unlocked lock, a RuntimeError is raised.

    There is no return value.
    """
    if self._locked:
        self._locked = False
        self._wake_up_first()
    else:
        raise RuntimeError('Lock is not acquired.')

So what it does is that every time you release your lock, it tries to wake the next waiter (the last coroutine that called this lock), which is :

fut = next(iter(self._waiters))

because the lock implement a queue for storing waiters

    def __init__(self, *, loop=None):
        self._waiters = collections.deque()
        self._locked = False

the next waiter is the first that called lock.

When executing your code :

Acquiring 1st
Trying to aacquire 2nd
Acquired 1st
trying to acquire Side
Acquired second
Acquired side

we clearly see that 2nd is waiting before side. So, when 1st is releasing it, it is the func2 that is going to wake up, not Side.

  • Related