1

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

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

6 Comments

consider the following code, pastebin.com/jLWv3Dm2, so from my understanding here, func2 does not wake up because it's in a queue, rather because the event loop is idle and that function is still unfinished and the queue's are only applicable to the locks
I ran your code which gave the results I mentionned in my answer ^^. What part don't you understand so I can be more specific/detailed ? Note: I changed your code so it runs without errors : asyncio.get_event_loop().run_until_complete(main()) for the last line
according to your answer, the waiter is awakened by lock.release, but the loop continues onto side_function as trying to acquire Side is printed before Acquired second
Awakened doesn't mean that it switch context instantly, it just notify that the code is ready to be executed, and the event loop will manages how/when to change the execution of code, in the same manner a single-core CPU scheduler works. You can check stackoverflow.com/a/51116910/10034177 which explains a lot of how event loops works :).
Ah, I see, so it just takes a higher priority than the other tasks, thanks!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.