Reputation: 2377
I have a system where two "processes" A
and B
run on the same asyncio
event loop.
I notice that the order of the initiation of processes matters - i.e. if I start process B
first then process B
runs all the time, while it seems that A
is being "starved" of resources vise-a-versa.
In my experience, the only reason this might happen is due to a mutex
which is not being released by B
, but in the following toy example it happens without any mutex
s being used:
import asyncio
async def A():
while True:
print('A')
await asyncio.sleep(2)
async def B():
while True:
print('B')
await asyncio.sleep(8)
async def main():
await B()
await A()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Is in python
the processes do not perform context-switch automatically? If not - how can I make both processes participate, each one in the time the other one is idle (i.e., sleeping)?
Upvotes: 3
Views: 942
Reputation: 51999
TLDR: Coroutines merely enable concurrency, they do not automatically trigger concurrency. Explicitly launch separate tasks, e.g. via create_task
or gather
, to run the coroutines concurrently.
async def main():
await asyncio.gather(B(), A())
Concurrency in asyncio
is handled via Tasks – a close equivalent to Threads – which merely consist of coroutines/awaitables – like Threads consist of functions/callables. In general, a coroutine/awaitable itself does not equate to a separate task.
Using await X()
means "start X
and wait for it to complete". When using several such constructs in sequence:
async def main():
await B()
await A()
this means launching B
first, and only launching A
after B
has completed: while async def
and await
allows for concurrency towards other tasks, B
and A
are run sequentially with respect to each other in a single task.
The simplest means to add concurrency is to explicitly create a task:
async def main():
# execute B in a new task
b_task = asyncio.create_task(B())
# execute A in the current task
await A()
await b_task
Note how B
is offloaded to a new task, while one can still do a final await A()
to re-use the current task.
Most async frameworks ship with high-level helpers for common concurrency scenarios. In this case, asyncio.gather
is appropriate to launch several tasks at once:
async def main():
# execute B and A in new tasks
await asyncio.gather(B(), A())
Upvotes: 5