Concurrency with Multiple Coroutines Using the asyncio Library in Python

Mastering Python can change your life; using Python effectively can greatly enhance your efficiency!—— Follow me to open a world of efficiency with Python.In AI development related to large models and agent development, we need to handle a large number of tasks simultaneously. Many people think of using multithreading (threading) for this, which is certainly feasible for lightweight concurrency. Today, I will introduce you to the asyncio library, which can handle a much larger volume of concurrency, especially on the server side where asyncio can achieve tens of thousands of concurrent connections, primarily used in web requests and MCP service tools.However, for other personal tasks, I prefer to use the concurrent.futures library, which encapsulates thread pools and process pools; I will introduce this in a separate article. In the field of concurrent programming in Python, the asyncio library is undoubtedly a shining star. It is based on an asynchronous programming model that can significantly improve the performance of programs in IO-intensive tasks, allowing Python programs to handle a large number of IO operations without becoming “stuck”.Concurrency with Multiple Coroutines Using the asyncio Library in PythonComparison of Multithreading and Multiprocessing Libraries

Tool Nature Suitable Scenarios Core Advantages
Multithreading (threading) Quick switching within a single CPU IO-intensive (simple scenarios) Lightweight, suitable for low concurrency
Multiprocessing (multiprocessing) Simultaneous operation across multiple CPUs Compute-intensive Fully utilizes multi-core, no GIL restrictions
Thread Poolconcurrent.futures (ThreadPoolExecutor) Tool for managing multithreading Batch IO tasks Reduces thread creation overhead, simple to use
Coroutines (asyncio) Manual task switching within a single thread High concurrency IO tasks Extremely efficient, suitable for tens of thousands of concurrent connections

Choosing Scenarios

Simple network requests/file processing: use thread pool (convenient).

Complex mathematical calculations/data analysis: use multiprocessing (utilizing multi-core).

To handle thousands of concurrent connections (e.g., servers): use coroutines (efficient).

Only occasionally need concurrency: use multithreading (simple and direct).

Understanding

Multithreading (threading): One person doing multiple things at the same time (but the CPU brain can only focus on one).

Multiprocessing (multiprocessing): Multiple people doing different things at the same time.

Thread Pool (concurrent.futures.ThreadPoolExecutor): Hiring a few fixed people to do work, distributing tasks.

Coroutines (asyncio): One person efficiently planning the order of tasks (completely in control of the pace).

Installation

asyncio is a built-in module in the Python standard library and does not require installation.

Core Components of asyncio

1. Core Base Classes (Coroutine Management and Event Loop)

asyncio.AbstractEventLoop: Abstract base class for event loops, defining interfaces for task scheduling, IO handling, etc. Different platforms have specific implementations (e.g., Unix uses SelectorEventLoop), commonly used methods include run_until_complete() (run coroutine), create_task() (create task), etc.

asyncio.Task: A schedulable object that encapsulates a coroutine (subclass of Future), can track status, obtain return values, commonly used methods include result() (get result), cancel() (cancel task).

asyncio.Future: Container for asynchronous operation results (similar to JS’s Promise), manually manages state, commonly used methods include set_result() (set successful result), await to wait for results.

asyncio.Coroutine: The coroutine object returned by async def functions, is the basic unit of asynchronous tasks, can be paused/resumed using await.

2. Task Scheduling and Running Functions (Starting Loop, Concurrent Execution)

asyncio.run(coro): Recommended way to start, automatically creates/closes the event loop, runs the coroutine (e.g., asyncio.run(main())).

asyncio.gather(*aws): Concurrently run multiple asynchronous objects, returns a list of results in order, supports capturing exceptions.

asyncio.create_task(coro): Wraps the coroutine as a Task and adds it to the schedule, achieving concurrency (non-blocking the current coroutine).

asyncio.wait(fs): Wait for a group of asynchronous objects, can set timeout and return conditions (e.g., return when the first one completes).

3. Synchronization and Communication Primitives (Multi-Coroutine Synchronization, Data Transmission)

Lock: Asynchronous mutex lock, ensures that only one coroutine accesses shared resources at a time (use async with lock).

Event: Asynchronous signal, coroutines can wait for signal triggers (await event.wait()), other coroutines use set() to send signals.

Condition: Condition variable based on locks, coroutines can wait for specific conditions to be met, other coroutines use notify() to wake up.

Queue: Asynchronous queue, implements the producer-consumer model, put() inserts data (blocks when full), get() retrieves data (blocks when empty).

Semaphore: Asynchronous semaphore, limits the number of concurrent coroutines (e.g., controls concurrent requests), use async with sem to acquire/release.

4. IO and Time-Related Functions (Asynchronous IO, Time Control)

asyncio.sleep(delay): Asynchronous sleep, pauses the coroutine for a specified time, releasing the event loop during this time.

asyncio.open_connection(): Asynchronously creates a TCP connection, returns read/write objects (reader/writer).

asyncio.start_server(): Starts an asynchronous TCP server, specifies client connection callbacks.

asyncio.to_thread(func): New in 3.9+, submits synchronous functions to the thread pool for execution, avoiding blocking the event loop.

5. Exceptions and Cancellation

TimeoutError: Thrown when wait_for()/wait() times out.

CancelledError: Thrown when a task is canceled using cancel().

asyncio.wait_for(aw, timeout): Sets a timeout for asynchronous operations.

Starting the First Coroutine

import asyncio
async def hello_coroutine(name):
    print(f"Hello, {name}! Coroutine starts executing")
    await asyncio.sleep(1)
    print(f"Hello, {name}! Coroutine execution ends")
# Start the event loop and run the coroutine
asyncio.run(hello_coroutine("Python Efficiency Studio"))

Pausing the coroutine, releasing event loop resources for IO operations

import asyncio
async def task1():
    print("Task 1 starts executing")
    await asyncio.sleep(2)  # Simulate 2 seconds of IO operation
    print("Task 1 execution completed")
async def task2():
    print("Task 2 starts executing")
    await asyncio.sleep(1)  # Simulate 1 second of IO operation
    print("Task 2 execution completed")
async def main():
    # Run task1 and task2 concurrently
    await asyncio.gather(task1(), task2())
asyncio.run(main())

Concurrently running multiple coroutines

import asyncio
async def calculate_square(x):
    await asyncio.sleep(1)
    return x * x
async def calculate_cube(x):
    await asyncio.sleep(1)
    return x * x * x
async def mycalc():
    # Concurrently run two calculation coroutines
    square_result, cube_result = await asyncio.gather(
        calculate_square(3),
        calculate_cube(2),
        return_exceptions=True  # Capture exceptions without interrupting other tasks
    )
    print(f"Square of 3: {square_result}")
    print(f"Cube of 2: {cube_result}")
asyncio.run(mycalc())

<span>return_exceptions=True</span> is used to capture exceptions

Wrapping coroutines as tasks and adding them to the event loop schedule

asyncio.create_task() can wrap a coroutine object into a Task object, which will be automatically added to the event loop’s task queue for scheduling execution. Compared to directly using coroutines, tasks can be better managed by the event loop and can also be named using the name parameter for easier debugging.

import asyncio
async def data_processing(task_name, delay):
    print(f"Task [{task_name}] starts processing data")
    await asyncio.sleep(delay)
    print(f"Task [{task_name}] data processing completed")
    return f"{task_name} processing result"
async def main3():
    # Create two tasks
    task1 = asyncio.create_task(data_processing("Task-1", 2), name="Task-1")
    task2 = asyncio.create_task(data_processing("Task-2", 1), name="Task-2")
    # Wait for tasks to complete and get results
    result1 = await task1
    result2 = await task2
    print("Task 1 result:", result1)
    print("Task 2 result:", result2)
asyncio.run(main3())

Setting timeout limits for coroutine execution to prevent indefinite blocking

asyncio.wait_for() can set a timeout for the execution of a coroutine. If the coroutine does not complete within the timeout seconds, it will throw an asyncio.TimeoutError exception, thus avoiding indefinite blocking of the coroutine.

import asyncio
async def long_running_task():
    print("Long-running task starts executing, expected to take 3 seconds")
    await asyncio.sleep(3)  # Simulate a 3-second task
    print("Long-running task execution completed")
    return "Task completion result"
async def test4():
    try:
        # Set timeout to 2 seconds
        result = await asyncio.wait_for(long_running_task(), timeout=2)
        print("Task result:", result)
    except asyncio.TimeoutError:
        print("Task execution timed out, terminated")
asyncio.run(test4())

Synchronization between coroutines, locking shared resource data

When multiple coroutines are executing concurrently, there may be situations where multiple coroutines operate on shared resources (e.g., modifying the same variable, writing to the same file), which can lead to data inconsistency. asyncio.Lock() provides a mutex mechanism to ensure that only one coroutine can acquire the lock and operate on shared resources at a time.

import asyncio
# Shared resource
shared_counter = 0
# Create lock object
lock = asyncio.Lock()
async def increment_counter(task_name):
    global shared_counter
    # Acquire lock
    async with lock:
        print(f"Task [{task_name}] acquired lock, current counter: {shared_counter}")
        # Simulate time-consuming operation on shared resource
        await asyncio.sleep(1)
        shared_counter += 1
        print(f"Task [{task_name}] released lock, updated counter: {shared_counter}")
async def test5():
    # Create 5 tasks to concurrently modify the shared counter
    tasks = [asyncio.create_task(increment_counter(f"Task-{i}")) for i in range(5)]
    await asyncio.gather(*tasks)
    print("Final counter value:", shared_counter)
asyncio.run(test5())

Application Scenarios

asyncio is mainly suitable for IO-intensive tasks. Here are some common application scenarios:

Network requests: Use the aiohttp library (based on asyncio) to send HTTP requests concurrently, significantly improving request efficiency.

File reading and writing: Asynchronously read and write large files to avoid blocking the main thread during IO operations.

Database operations: Use asyncpg (PostgreSQL asynchronous driver), motor (MongoDB asynchronous driver), and other libraries for asynchronous database operations.

WebSocket services: Implement high-performance WebSocket services based on asyncio to handle real-time communication scenarios.

Historical Python Articles:

Basic articles for learning Python

Basics of Basics (suitable for beginners)

Must-Know Python OS Standard Library

Overview of Python Built-in Functions, Simple and Intuitive

Applications of Regular Expressions in Office Work

Must-Know Python sys Standard Library

Quick Reference for Python Knowledge

Using Domestic Sources for pip to Speed Up Python Library Installation

Usage of Python Lambda

Must-Know Python Socket

Four Ways to Package Python Projects into EXE

Common Programming Reference Atlas for Python

Common Programming Knowledge Reference Atlas for Python II

Four Progress Bars in Python

Calculating Timestamps in Python

Seven Ways to Read and Write Configuration Files in Python

Mathematics and Computation

Special Topic: The Three Musketeers of Python Mathematical Operations – Pandas

Special Topic: The Three Musketeers of Python Mathematical Operations – Numpy

Special Topic: The Three Musketeers of Python Mathematical Operations – A Brief Discussion on Scipy

The Wonderful Collision of Python and Mathematics, Making Computation Easy

Data Analysis and Visualization

Python Visualization with Bokeh Library

Python Visualization with Pyecharts

Matplotlib Turns Mathematics into Visualization

Python Visualization Charts with Seaborn Library

Cute Python Hand-Drawn Chart Library – CuteCharts

Web Scraping

Notes on the New Version of Selenium 4.0

Must-Know Python BeautifulSoup Library

Practical: Easily Implement HTTP Requests with Requests Library

Scrapy to be Supplemented

urllib to be Supplemented

Playwright Library – Using Python to Help You with Browser Tasks

Image Processing

Must-Know Python OpenCV Library

Python Image Processing Library – Pillowimageio to be SupplementedNatural Language Processing

Chinese Word Segmentation with Python Jieba Library

Python Natural Language Processing with spaCy Library

NLTK to be Introduced

Office Applications

Python Task Scheduling Tool Schedule, Let It Help You Work on Time

Automating Word Document Processing with Python-docx

Five Ways to Write Data into CSV Files with Python

Several Ways to Read and Write Files with Python

Getting Started with Python Documentation Generation using Sphinx Library

Python’s Swiss Army Knife for PDF Operations – PyMuPDF Library

PyPDF2 to be Supplemented

Python Automation Library PyAutoGUI

Industrial ApplicationsEfficiency Applications of Python in Mechanical Drawing with CadQuery LibrarySoftware Interfaces and GamesPyQt Development Library to be IntroducedTkinter Interface Library to be IntroducedUsing Pygame to Teach You How to Create a Plane Battle GameProject Engineering Related

Getting Started with Python’s Automated Testing Library Pytest

Python Project Logging with Loguru Library

Must-Know Python Threading Library

Using Lightweight Database SQLite in Python Projects

Must-Know Python Cryptography Library

Exploring the Faker Library, a Powerful Tool for Generating Various Test Data

Thread Pool Relatedconcurrent.futures to be Introduced

Fun Projects

Exploring the Stars and the Sea with Python’s Skyfield Library

Python’s Super Fun Library – Turtle Graphics

Deep Use of Python in Music Creation [Audio Processing Related References]

System Hardware, etc.

Python’s System Monitoring and Process Management Tool – Psutil Library

platformdirs to be Supplemented

pypiwin32 to be Supplemented

pyserial to be Supplemented

pywifi to be Supplemented

Python Web Development Related

Flask to be IntroducedDjango to be IntroducedVideo Processingffmpeg-python to be UpdatedUsing Python to Accurately Dub Videos in BulkPython Development, a Tool for Translating Chinese Videos into 30+ Languages

Others

Python Application Universe, All Uses Explained

Organizing Personal Library Files Installed in pip list

Popular Articles

Python Video Processing Tools, Assisting Learning and Recreation

Comprehensive Overview of Domestic GPU Rental Platforms (The Most Comprehensive on the Internet)

Notes on the New Version of Selenium 4.0

Four Progress Bars in Python

Python’s Super Fun Library – Turtle Graphics

Must-Know Python BeautifulSoup Library

Must-Know Python OpenCV Library

Must-Know Python Threading Library

Must-Know Python OS Standard Library

Reading Large CSV Files with Python and Searching Content

Python Task Scheduling Tool Schedule, Let It Help You Work on Time

Creating a Custom Pomodoro Timer EXE with Python to Improve Work Efficiency and Easily Manage Time

Using Domestic Sources for pip to Speed Up Python Library Installation

Five Ways to Write Data into CSV Files with Python

Ways to Read and Write Files with Python

Four Ways to Package Python Projects into EXE

Python Automation Library PyAutoGUI

Keyboard and Mouse Automation with Pynput Library

Historical Articles on Intelligent Agents

Detailed Analysis and Operation Modification of Intelligent Agent OWL Architecture

Detailed Breakdown of OpenManus Intelligent Agent

Historical Articles on AIRecord of Deploying Speech-to-Text Deep Learning Tools on Local CPU

Guide to Feeding DeepSeek API for Multi-Turn Conversations with Python

Alternative Solutions for Busy DeepSeek Servers (Ongoing Follow-Up)

The Most Comprehensive Alternative Solutions and Evaluations for DeepSeek on the Internet

Building Your Own DeepSeek R1 and Knowledge Base – A Step-by-Step Guide

Instructions for Feeding Suno API with Python

Comprehensive Overview of Domestic GPU Rental Platforms (The Most Comprehensive on the Internet)

Recreating Your Digital Avatar – Voice Avatar

Case Applications

A Tool for Precisely Inserting Video Subtitles Created with Python

Python Video Processing Tools, Assisting Learning and Recreation

Implementing 10 Average Algorithms with Python

Creating Your Own Piano Keyboard with Pygame (Part 1)

Python Implementation of the Knapsack Algorithm

Practical: Creating a Device QR Code Generator with Python

Practical: I Used MoviePy to Create a Simple Version of a Video Editing Tool

Reading Large CSV Files with Python and Searching Content

Automatically Sending Emails with Python for Remote Device Maintenance

Python Task Scheduling Tool Schedule, Let It Help You Work on Time

Creating a Custom Pomodoro Timer EXE with Python to Improve Work Efficiency and Easily Manage Time

An Unconventional Python Application Practical Case

Using Python to Help You Research and Make Money

Deep Use of Python in Music Creation

Efficiently Using Python to Achieve Your Labeling Freedom

Implementing 10 Average Algorithms with Python

Leave a Comment