Asynchronous Programming in Python
- Summary
-
Discussion
- What's the background for adopting asynchronous programming in Python?
- Which are the basic constructs that enable asynchronous programming in Python?
- Which are some basic API calls worth knowing for a beginner?
- What some essential tips when working with asynchronous Python code?
- Which Python packages or modules enable asynchronous programming?
- How can I debug asynchronous Python code?
- How's the performance of asynchronous programming in Python?
- Milestones
- Sample Code
- References
- Further Reading
- Article Stats
- Cite As
Asynchronous programming is a programming paradigm that enables better concurrency, that is, multiple threads running concurrently. In Python, asyncio
module provides this capability. Multiple tasks can run concurrently on a single thread, which is scheduled on a single CPU core.
Although Python supports multithreading, concurrency is limited by the Global Interpreter Lock (GIL). The GIL ensured that only one thread can acquire the lock at a time. Asynchronous programming doesn't solve the GIL limitation but still enables better concurrency.
With multiprocessing, task scheduling is done by the operating system. With multithreading, the Python interpreter does the scheduling. In Python's asynchronous programming, scheduling is done by what's called the event loop. Developers can specify in their code when a task voluntarily gives up the CPU so that the event loop can schedule another task. For this reason, this is also called cooperative multitasking.
Discussion
-
What's the background for adopting asynchronous programming in Python? Python has long supported both multiprocessing and multithreading. Multiprocessing can make use of multiple CPU cores but at the overhead cost of inter-process communication (IPC). Each process has its own Python interpreter and GIL. Multithreading avoids the IPC overhead by having all threads share the same memory space. But scheduling these threads is limited by the GIL.
For I/O-bound threads, if a thread waits on I/O, GIL is automatically released and given to another thread. For CPU-bound threads, GIL doesn't give us a mechanism to run all threads concurrently, even if the system has multiple CPU cores. In either case, asynchronous programming helps us achieve better concurrency (for the single CPU core scenario).
GIL is used in Python's default CPython implementation. CPython's use of GIL makes memory management thread safe. Other implementations such as JPython and IronPython are not limited by the GIL. They solve memory management and thread synchronization within their virtual machines (JVM/CLR). Historically, CPython's GIL made it easy to interface with C extensions in a thread-safe manner, which made Python popular.
-
Which are the basic constructs that enable asynchronous programming in Python? Executing asynchronous code requires an event loop. Python provides a default event loop implementation. It's also possible to use alternative implementations such as
uvloop
, which has shown at least 2x better performance.The event loop executes coroutines, one at a time. A coroutine is simply a method or function defined with
async
keyword. A coroutine needs to be added to the event loop, such as usingasyncio.run()
.When a coroutine waits for the result of another coroutine using the
await
keyword, it gets suspended. The event loop then schedules another coroutine that's ready to run. More formally, a coroutine waits on an awaitable object. This can be another coroutine, a Task or a Future.A Task is really a wrapper on a coroutine typically created with
asyncio.create_task()
. Via the Task, the coroutine is automatically scheduled.A Future is a low-level object that represents the eventual result of an asynchronous operation. When a Future is awaited, it means the coroutine will wait until the Future is resolved in some other place.
-
Which are some basic API calls worth knowing for a beginner? We can create multiple event loops in a thread but only one can be active at a time. Relevant APIs to learn are
asyncio.new_event_loop()
,asyncio.set_event_loop()
,asyncio.get_event_loop()
andasyncio.get_running_loop()
. Ifasyncio.get_event_loop()
is called without a prior call toasyncio.set_event_loop()
, a new event loop is automatically created and set as the current one.Event loop methods include
run_until_complete()
,run_forever()
,is_running()
,is_closed()
,stop()
andclose()
. Callbacks can be scheduled withcall_soon()
,call_soon_theadsafe()
,call_later()
andcall_at()
. Another useful method of loop iscreate_task()
, which is also possible withasyncio.create_task()
.Loop also includes methods to manage network connections and entities:
create_connection()
,create_datagram_endpoint()
,create_server()
,sendfile()
,start_tls()
, plus low-level methods to work directly with sockets.asyncio.run()
runs a coroutine, in the process creating an event loop and closing it at the end. This can't be called if an event loop is already running in the current thread. To run many tasks concurrently, callasyncio.gather()
. Other useful methods includeasyncio.wait()
,asyncio.wait_for()
,asyncio.current_task()
andasyncio.all_tasks()
. To sleep asynchronously, useasyncio.sleep()
rather thantime.sleep()
. -
What some essential tips when working with asynchronous Python code? An event loop can run in any thread but it must run in the main thread if it has to handle signals and execute subprocesses.
To schedule callbacks from another thread, use
loop.call_soon_threadsafe()
rather thanloop.call_soon()
.Note that calling a coroutine function doesn't actually execute the function and return the result. It only returns a coroutine object, which must be passed into
asyncio.run()
.Avoid CPU-bound blocking calls. For example, if a CPU-intensive computation takes 1 second, all asynchronous tasks and I/O operations will be delayed by 1 second. Use
loop.run_in_executor()
along withconcurrent.futures.ThreadPoolExecutor
to execute blocking code in a different thread or even a different process. -
Which Python packages or modules enable asynchronous programming? Module
asyncio
is the main one for asynchronous programming in Python. Whilegevent
andeventlet
achieve similar behaviour,asyncio
is easier and more approachable even for non-experts.Use module
threading
for I/O-bound concurrent operations. Usemultiprocessing
for CPU-bound parallel computations. Equivalently,concurrent.futures.ThreadPoolExecutor
andconcurrent.futures.ProcessPoolExecutor
can be used as these provide a simpler API.Among the alternatives to
asyncio
arecurio
andtrio
. Both these are somewhat compatible withasyncio
. Trio in particular claims to focus on usability and correctness.Timo Furrer curates a list of asynchronous Python frameworks and packages. Among the web frameworks are aiohttp, Tornado, Sanic, Vibora, Quart and FastAPI. For message queues we have aioamqp, pyzmq, and aiokafka. For testing we have aiomock, asynctest, and pytest-asyncio. For databases we have asyncpg, aioredis, and aiomysql. The popular
requests
library with asynchronous support is calledrequests-async
.To make use of multicores or even multiple machines, Apache Spark (via PySpark) should be considered. Meanwhile, work is going on to bring multicore support within the interpreter via projects Software Transactional Memory, Dask and PyParallel.
-
How can I debug asynchronous Python code? By default,
asyncio
runs in production mode. There are many ways to run it in debug mode:- Set
PYTHONASYNCIODEBUG
environment variable to 1 - Use
-X dev
command line option - Use argument
debug=True
toasyncio.run()
(default isdebug=False
) - Call
loop.set_debug(True)
(can inspect current value withloop.get_debug()
)
Adjust the logging level by calling for example
logging.getLogger("asyncio").setLevel(logging.WARNING)
. Python interpreter will emit log messages for never-awaited coroutines or never-retrieved exceptions. In debug mode, we get more information about these issues.The third-party library aiodebug might help in debugging asyncio programs.
- Set
-
How's the performance of asynchronous programming in Python? Performance of
asyncio
falls short of the what Node.js and Go can achieve. However, whenasyncio
is used along withuvloop
(for the event loop) andhttptools
(for HTTP), it gives best performance in terms of both throughput and response time.While
aiohttp
offers asynchronous HTTP, it has a slow HTTP parser. This limitation is addressed byhttptools
. However, in one experiment,aiotools
outperformed its synchronous equivalents such as Flask or Django. It was 26x faster than Flask.In another experiment involving multiple HTTP calls, total runtime was 13.1 secs (synchronous, 1 thread), 1.7 secs (synchronous, 20 threads), and 1.3 secs (aiohttp, 1 thread). When used with Gunicorn web server,
aiohttp
was 2x faster than Flask when a single Gunicorn worker was used.Another experiment noted that it's unfair to compare synchronous and asynchronous web frameworks without adjusting the number of workers. Synchronous frameworks must be given more workers since the entire worker blocks on I/O. With more workers, they can utilize all the CPU cores. This experiment showed that synchronous frameworks did better.
Milestones
2002
2012
Work towards standardizing asynchronous support in Python begins as part of PEP 3156, titled Asynchronous IO Support Rebooted: the "asyncio" Module. This supersedes an earlier proposal (PEP 3153). It also acknowledges earlier work of Twisted, Tornado, and ZeroMQ that have supported asynchronous calls in their packages.
2015
Python 3.5 is released with support for awaitable objects, coroutine functions, asynchronous iteration, and asynchronous context managers. This includes syntaxes async def
, async for
, async with
and await
. Any object that has the __await()__
method can be awaited. These changes are expected to make asynchronous programming a lot easier. The async/await syntax of this release is inspired by yield from
of Python 3.3 and asyncio
module of Python 3.4.
2016
2018
Python 3.7 is released in which async
and await
are now keywords. This release improves the asyncio
module in terms of usability and performance. For example, assuming main()
is our coroutine, scheduling a coroutine to the event loop is as simple as calling asyncio.run(main())
. This simplifies the earlier syntax of loop = asyncio.get_event_loop()
and loop.run_until_complete(main())
.
Sample Code
References
- Ajitsaria, Abhinav. 2018. "What is the Python Global Interpreter Lock (GIL)?" Real Python, March 6. Updated 2018-06-10. Accessed 2020-06-27.
- Anderson, Jim. 2019. "Speed Up Your Python Program With Concurrency." Real Python, January 14. Updated 2020-05-31. Accessed 2020-06-27.
- Baatout, Amine. 2018. "Multithreading VS Multiprocessing in Python." Contentsquare Engineering, on Medium, December 5. Accessed 2020-06-27.
- Cannon, Brett. 2016. "How the heck does async/await work in Python 3.5?" Blog, Tall, Snarky Canadian, February 11. Accessed 2020-06-27.
- Chauhan, Shailendra. 2015. "Brief history of node.js and io.js." DotNetTricks, December 31. Accessed 2020-07-01.
- Coghlan, Nick. 2015. "Efficiently Exploiting Multiple Cores with Python." Python Notes, Curious Efficiency, June 21. Accessed 2020-06-27.
- Furrer, Timo. 2020. "timofurrer / awesome-asyncio." GitHub, May 18. Accessed 2020-06-27.
- Hettinger, Raymond. 2019. "What’s New In Python 3.8." Python Docs, October 14. Accessed 2020-06-30.
- Kennedy, Michael. 2019. "Demystifying Python's Async and Await Keywords." JetBrains TV, on YouTube, February 21. Accessed 2020-06-30.
- Lefkowitz, Glyph. 2002. "Twisted 1.0 Developer Platform." Email, on LWN.net, October 21. Accessed 2020-06-30.
- Li, Qian. 2020. "A better way for asynchronous programming: asyncio over multi-threading." Towards Data Science, on Medium, February 16. Accessed 2020-06-27.
- Mathôt, Sebastiaan. 2017. "Python tricks: Demystifying async, await, and asyncio." YouTube, August 3. Accessed 2020-06-27.
- Murray, R. David. 2014. "What’s New In Python 3.4." Python Docs, March 16. Accessed 2020-06-30.
- Notna, Andrei. 2019. "Intro to Async Concurrency in Python vs. Node.js." Medium, February 6. Accessed 2020-06-27.
- Paterson, Cal. 2020. "Async Python is not faster." June. Accessed 2020-06-27.
- Paxos. 2017. "Should I Migrate to an Async Framework?" Blog, Paxos, October 12. Accessed 2020-06-27.
- Pranskevichus, Elvis. 2018. "What’s New In Python 3.7." Python Docs, June 27. Accessed 2020-06-30.
- Pranskevichus, Elvis, and Yury Selivanov. 2015. "What’s New In Python 3.5." Python Docs, November 14. Accessed 2020-06-30.
- Pranskevichus, Elvis, and Yury Selivanov. 2016. "What’s New In Python 3.6." Python Docs, December 22. Accessed 2020-06-30.
- PyPI. 2015. "asyncio 3.4.3: Release History." PyPI, March 10. Accessed 2020-06-27.
- PyPI. 2019. "requests-asynchronous 0.6.2." PyPI, June 21. Accessed 2020-06-28.
- Python Docs. 2020a. "Developing with asyncio." Python Docs. v3.8.3, June 30. Accessed 2020-06-30.
- Python Docs. 2020b. "asyncio — Asynchronous I/O." Python Docs. v3.8.3, June 30. Accessed 2020-06-30.
- Python Docs. 2020c. "Event Loop." Python Docs. v3.8.3, June 30. Accessed 2020-06-30.
- Python Docs. 2020d. "concurrent.futures — Launching parallel tasks." Python Docs. v3.8.3, June 30. Accessed 2020-06-30.
- Python Docs. 2020e. "Coroutines and Tasks." Python Docs. v3.8.3, June 30. Accessed 2020-06-30.
- Python Wiki. 2017. "GlobalInterpreterLock." Python Wiki, August 2. Accessed 2020-06-27.
- Selivanov, Yury. 2016. "uvloop: Blazing fast Python networking." Blog, MagicStack, May 3. Accessed 2020-06-27.
- Shaff, Dean. 2020. "Asynchronous vs Synchronous Python Performance Analysis." Stack Abuse. Accessed 2020-06-27.
- Smith, Nathaniel J. 2020. "Trio: a friendly Python library for asynchronous concurrency and I/O." v0.16.0, Revision 7ef6f09f, June 10. Accessed 2020-06-27.
- Stanley, Kyle. 2019. "What are the advantages of asyncio over threads?" Discussion Board, Python.org, December 18. Accessed 2020-06-30.
- Stinner, Victor. 2019. "Why use asyncio?" Asyncio Documentation, December 5. Accessed 2020-06-30.
- Thakur, Ankush. 2020. "Top 5 Asynchronous Web Frameworks for Python." Geekflare, January 19. Accessed 2020-06-27.
- Velotio Technologies. 2018. "An Introduction to Asynchronous Programming in Python." Velotio Technologies, on Medium, August 24. Accessed 2020-06-27.
- van Rossum, Guido. 2012. "PEP 3156 -- Asynchronous IO Support Rebooted: the 'asyncio' Module." PSF, December 21. Accessed 2020-06-30.
Further Reading
- Cannon, Brett. 2016. "How the heck does async/await work in Python 3.5?" Blog, Tall, Snarky Canadian, February 11. Accessed 2020-06-27.
- Anderson, Jim. 2019. "Speed Up Your Python Program With Concurrency." Real Python, January 14. Updated 2020-05-31. Accessed 2020-06-27.
- Solomon, Brad. 2019. "Async IO in Python: A Complete Walkthrough." Real Python, January 16. Updated 2020-06-19. Accessed 2020-06-27.
- Mathôt, Sebastiaan. 2017. "Python tricks: Demystifying async, await, and asyncio." YouTube, August 3. Accessed 2020-06-27.
- Kennedy, Michael. 2019. "Demystifying Python's Async and Await Keywords." JetBrains TV, on YouTube, February 21. Accessed 2020-06-30.
- Stanley, Kyle. 2019. "What are the advantages of asyncio over threads?" Discussion Board, Python.org, December 18. Accessed 2020-06-30.