Table of Contents
- What is Concurrency?
- Writing Concurrent Code in Python
- Libraries for Asynchronous Programming
- Getting Started with asyncio
- Most Popular asyncio Functions
- Other Relevant Information
- Conclusion
Python, known for its synchronous code execution, processes tasks line by line, causing delays when tasks take time to run. In a previous article, we discussed parallelization, the Global Interpreter Lock (GIL), and its impact on parallel execution.
To overcome this, we turn to concurrency.
What is Concurrency?
Concurrency involves running multiple tasks simultaneously by efficiently managing their wait times.
Writing Concurrent Code in Python
To achieve concurrency in Python, we turn to asynchronous programming, briefly discussed here.
Libraries for Asynchronous Programming
Three notable libraries for asynchronous programming are:
- asyncio
- anyio
- aiohttp
This article focuses on asyncio, the foundation for anyio and aiohttp.
Getting Started with asyncio
asyncio enables writing concurrent code using the async/await syntax. It serves as the base for various Python asynchronous frameworks, offering high-performance solutions for network and web servers, database connections, distributed task queues, and more.
asyncio is particularly suitable for IO-bound and high-level structured network code. Its high-level APIs allow:
- Running Python coroutines concurrently
- Performing network IO and IPC
- Controlling subprocesses
- Distributing tasks via queues
- Synchronizing concurrent code
Low-level APIs are also available for creating and managing event loops, implementing efficient protocols, and bridging callback-based libraries with async/await syntax.
Asynchronous Programming in Action
Synchronous Code
#sync.py
import time
def print_message(message, delay):
time.sleep(delay)
print(message)
def main():
print_message("Hello", 2)
print_message("Sync", 1)
print_message("World", 3)
if __name__ == "__main__":
start_time = time.time()
main()
end_time = time.time()
print(f"Total execution time: {end_time - start_time} seconds")
Asynchronous Code
#async.py
import asyncio
import time
# Define a simple asynchronous function
async def print_message(message, delay):
# Simulate some asynchronous operation, like I/O or network request
await asyncio.sleep(delay)
print(message)
# Define the main asynchronous function
async def main():
# Use asyncio.gather to run multiple asynchronous functions concurrently
# Wait for each task to complete in order
await asyncio.gather(
print_message("Async", 2),
print_message("Hello", 1),
print_message("World", 3)
)
# Run the event loop to execute the asynchronous code
if __name__ == "__main__":
# Record the start time for measuring execution time
start_time = time.time()
# Run the main asynchronous function using asyncio.run()
asyncio.run(main())
# Calculate and print the total execution time
end_time = time.time()
print(f"Total execution time: {end_time - start_time} seconds")
Walkthrough of Asynchronous Code:
- Import the required libraries:
asyncio
andtime
. - Define an asynchronous function
print_message(message, delay)
. This function simulates asynchronous operations usingasyncio.sleep(delay)
and prints the given message. - Define the main asynchronous function
main()
. It usesasyncio.gather
to concurrently run multiple instances of theprint_message
function with different messages and delays. - Check if the script is being run directly using
if __name__ == "__main__":
. - Record the start time using
start_time = time.time()
. - Run the main asynchronous function using
asyncio.run(main())
. - Record the end time using
end_time = time.time()
. - Print the total execution time:
print(f"Total execution time: {end_time - start_time} seconds")
.
Most Popular asyncio Functions
-
run()
: Create an event loop, run a coroutine, and close the loop. -
Runner
: A context manager simplifying multiple async function calls. -
Task
: Task object. -
TaskGroup
: A context manager holding a group of tasks, providing a way to wait for all to finish. -
create_task()
: Start an asyncio Task and return it. -
current_task()
: Return the current Task. -
all_tasks()
: Return all unfinished tasks for an event loop. -
await sleep()
: Sleep for a number of seconds. -
await gather()
: Schedule and wait for things concurrently. -
await wait_for()
: Run with a timeout. -
await shield()
: Shield from cancellation. -
await wait()
: Monitor for completion. -
timeout()
: Run with a timeout. -
to_thread()
: Asynchronously run a function in a separate OS thread. -
run_coroutine_threadsafe()
: Schedule a coroutine from another OS thread. -
for in as_completed()
: Monitor for completion with a for loop.
Other Relevant Information
-
Debug Mode:
- Enable by setting
PYTHONASYNCIODEBUG=1
, using Python Development Mode, passingdebug=True
toasyncio.run()
, or callingloop.set_debug()
. - Configure the logger level to
logging.DEBUG
. - Display ResourceWarning warnings using
-W default
command line option.
- Enable by setting
With debug mode:
- asyncio checks for unawaited coroutines.
- Non-threadsafe asyncio APIs raise exceptions if called from the wrong thread.
- I/O selector execution time is logged if it takes too long.
- Callbacks longer than 100 milliseconds are logged.
Conclusion
Incorporate asyncio into your Python projects for efficient concurrent and asynchronous programming. Explore its rich set of functions and keep in mind the debugging options for smoother development.
I will be writing an article on anyio. The asynchronous programming library used by fastapi and starlette to handle requests and other tasks.
In the mean time, follow me, jump to comments, and leabe positive reactions.
Happy asynchronous coding!!!
Top comments (0)