concurrency in python 3.x
Python 3.x introduced coroutines (subroutines which can be entered, exited, and resumed at many different points), which can be created and executed using the async/await
syntax provided by the asyncio
package.
Python is single-threaded, so there is a single event loop on which (co)routines can run, meaning that parallelism cannot be achieved: asyncio
provides tools for concurrent programming.
async def
turns a function into a coroutine- note: if the function itself doesn’t use
await
in its body, it will still run as a whole
- note: if the function itself doesn’t use
await <coroutine>
signals that the function is waiting for the result of the coroutine and is yielding control until then, so that other coroutine can run in the meanwhile: it does not block.
Coroutines do not actually run when simply invoked, as in the main
of the following snippet
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
say_after(delay, what)
asyncio.run(main())
There are three ways to run coroutines:
-
Using
asyncio.run(<coroutine>)
, as in the last line of the above snippet -
Sequentially:
async def main():
await say_after(1, 'hello')
await say_after(2, 'world')
asyncio.run(main())
# output:
# hello
# world
In this situation, the execution in the above snippet is sequential, not concurrent (the total elapsed time will be 1 + 2 = 3 seconds).
- Concurrently (making use of
asyncio.Task
):
async def main():
t1 = asyncio.create_task(say_after(1, 'hello'))
t2 = asyncio.create_task(say_after(2, 'world'))
await t1
await t2
asyncio.run(main())
Now the execution will complete in 2 seconds, because asyncio.create_task
schedules the task to “run soon” (as per docs).