•11 min read

Tìm hiểu chuyên sâu Python Asyncio: Coroutine, Task và Event Loop vào năm 2026

Tìm hiểu chuyên sâu Python Asyncio: Coroutine, Task và Event Loop vào năm 2026

Lập trình bất đồng bộ trong Python đã phát triển từ một tiện ích bổ sung thử nghiệm trong Python 3.4 thành mô hình kiến trúc mặc định cho các microservice hiệu suất cao hiện đại, cổng API và các đường ống dữ liệu truyền trực tuyến. Được hỗ trợ bởi các framework như FastAPI, Litestar và Sanic, các backend Python thường xuyên xử lý hàng chục nghìn kết nối đồng thời.

Tuy nhiên, mặc dù được sử dụng rộng rãi, asyncio vẫn là một trong những hệ thống bị hiểu lầm nhiều nhất trong Python. Các nhà phát triển thường xuyên gây ra tắc nghẽn vòng lặp sự kiện, trộn lẫn các lệnh gọi chặn đồng bộ bên trong ngữ cảnh thực thi bất đồng bộ, hoặc dựa vào các API cũ lỗi thời như asyncio.gather mà không có sự cách ly ngoại lệ thích hợp.

Trong phần tìm hiểu sâu này, chúng ta sẽ khám phá cách runtime asyncio của Python thực sự hoạt động bên dưới, so sánh asyncio.TaskGroup với gather cũ, trình bày cách an toàn để chuyển tải công việc bị giới hạn bởi CPU bằng cách sử dụng asyncio.to_thread, và xây dựng một đường ống worker giới hạn tốc độ có độ đồng thời cao.

Audio Briefing
0:00 / 0:00
Part of a Series

Chuỗi Backend Python hiệu suất cao

Part 4 of 4

Kiến trúc vòng lặp sự kiện: Cách Python xử lý 10.000 socket trên một luồng

Trung tâm của asyncio là một Vòng lặp sự kiện đơn luồng. Thay vì cấp phát một luồng OS cho mỗi kết nối (tiêu tốn ~8 MB bộ nhớ stack cho mỗi luồng và gây ra các hình phạt chuyển đổi ngữ cảnh OS nặng nề), asyncio đa hợp các hoạt động I/O không chặn trên một luồng duy nhất bằng cách sử dụng các nguyên thủy thăm dò của hệ điều hành (epoll trên Linux, kqueue trên macOS và IOCP trên Windows).

+---------------------------------------------------------------------------------+
|                                 Single OS Thread                                |
|                                                                                 |
|   +-------------------------------------------------------------------------+   |
|   |                           Asyncio Event Loop                            |   |
|   |                                                                         |   |
|   |   1. Poll OS Kernel Socket Ready State (epoll / kqueue)                 |   |
|   |   2. Resume Waiting Coroutines (send next() value)                      |   |
|   |   3. Suspend at 'await' boundary (yield socket descriptor to loop)      |   |
|   |   4. Run Scheduled Timers and Callbacks                                 |   |
|   +-------------------------------------------------------------------------+   |
|                                        |                                        |
|                                        v                                        |
|   +-------------------------------------------------------------------------+   |
|   |                          OS Kernel Non-Blocking I/O                     |   |
|   |   [ Socket 1: Reading ]  [ Socket 2: Writing ]  [ Socket 3: Connected ] |   |
|   +-------------------------------------------------------------------------+   |
+---------------------------------------------------------------------------------+

Khi một hàm được khai báo với async def, việc gọi nó không thực thi thân hàm ngay lập tức. Thay vào đó, nó trả về một đối tượng coroutine. Một coroutine là một generator nâng cao có thể tạm dừng thực thi tại bất kỳ biểu thức await nào và trả quyền điều khiển về vòng lặp sự kiện cho đến khi hoạt động I/O được chờ đợi sẵn sàng.


Advertisement

Đồng thời có cấu trúc hiện đại: asyncio.TaskGroup vs asyncio.gather

Trước Python 3.11, cách diễn đạt tiêu chuẩn để chạy nhiều hoạt động bất đồng bộ đồng thời là asyncio.gather. Mặc dù phổ biến, gather có một lỗi kiến trúc nghiêm trọng: các ngoại lệ không được xử lý không hủy các tác vụ anh em.

Nếu một tác vụ thất bại trong gather(), các tác vụ khác tiếp tục chạy như các coroutine "mồ côi" tách rời trong nền, tiêu tốn bộ nhớ, giữ các kết nối cơ sở dữ liệu mở và tạo ra các lỗi âm thầm.

Vấn đề cũ: Các tác vụ mồ côi trong asyncio.gather

import asyncio

async def fetch_user(user_id: int):
    await asyncio.sleep(0.5)
    if user_id == 2:
        raise ValueError("User database connection timeout!")
    return {"id": user_id, "name": f"User {user_id}"}

async def fetch_orders(user_id: int):
    # This keeps running and executing even after fetch_user fails!
    await asyncio.sleep(2.0)
    print("Orders fetched (wasted database compute!)")
    return ["Order_101", "Order_102"]

async def main_legacy():
    try:
        results = await asyncio.gather(
            fetch_user(2),
            fetch_orders(2)
        )
    except ValueError as e:
        print(f"Caught error: {e}")
        # fetch_orders is still running in the background!

Giải pháp hiện đại: asyncio.TaskGroup (Python 3.11+)

Python 3.11 đã giới thiệu đồng thời có cấu trúc thông qua asyncio.TaskGroup. Khi được sử dụng bên trong một trình quản lý ngữ cảnh bất đồng bộ (async with), nếu bất kỳ tác vụ nào trong nhóm gây ra một ngoại lệ, TaskGroup tự động hủy tất cả các tác vụ anh em còn lại, dọn dẹp tài nguyên và đưa ra một ExceptionGroup:

import asyncio

async def fetch_user(user_id: int):
    await asyncio.sleep(0.5)
    if user_id == 2:
        raise ValueError("User database connection failed!")
    return {"id": user_id, "name": f"User {user_id}"}

async def fetch_orders(user_id: int):
    try:
        await asyncio.sleep(2.0)
        return ["Order_101", "Order_102"]
    except asyncio.CancelledError:
        print("fetch_orders was cleanly cancelled because fetch_user failed!")
        raise

async def main_modern():
    try:
        async with asyncio.TaskGroup() as tg:
            task1 = tg.create_task(fetch_user(2))
            task2 = tg.create_task(fetch_orders(2))
        
        # Execution reaches here only if both tasks succeed
        print(task1.result(), task2.result())
    except* ValueError as eg:
        # Python 3.11+ exception group pattern matching
        for error in eg.exceptions:
            print(f"Handled error in TaskGroup: {error}")

if __name__ == "__main__":
    asyncio.run(main_modern())

Tội lỗi lớn nhất: Tắc nghẽn vòng lặp sự kiện và cách khắc phục

Vì vòng lặp sự kiện asyncio chạy trên một luồng duy nhất, bất kỳ lệnh gọi chặn đồng bộ nào cũng làm đóng băng toàn bộ ứng dụng. Nếu một endpoint thực thi một vòng lặp nặng CPU, gọi time.sleep(), hoặc sử dụng một trình điều khiển cơ sở dữ liệu đồng bộ như psycopg2 thay vì asyncpg, mọi người dùng đồng thời khác đang chờ trên máy chủ đó đều bị chặn.

Minh họa tắc nghẽn

import asyncio
import time

# WRONG: Synchronous blocking call inside async function!
async def bad_endpoint():
    # Freezes the ENTIRE event loop for 2 full seconds!
    # No other requests can be accepted or processed during this window.
    time.sleep(2.0)
    return {"status": "ok"}

Giải pháp 1: Chuyển tải các lệnh gọi chặn với asyncio.to_thread

Đối với các SDK bên thứ ba đồng bộ (chẳng hạn như boto3 cho AWS S3 hoặc PIL/Pillow để xử lý hình ảnh), hãy sử dụng asyncio.to_thread(). Điều này ủy quyền lệnh gọi chặn cho nhóm luồng nội bộ của Python mà không làm đình trệ vòng lặp sự kiện:

import asyncio
from PIL import Image

def resize_image_sync(filepath: str, output_path: str):
    # CPU-heavy image compression
    with Image.open(filepath) as img:
        img.thumbnail((800, 800))
        img.save(output_path, "JPEG", quality=85)

async def handle_image_upload(filepath: str, output_path: str):
    # Cleanly offloaded to background thread pool
    await asyncio.to_thread(resize_image_sync, filepath, output_path)
    return {"status": "optimized"}

Mẫu sản xuất: Đồng thời có giới hạn với asyncio.Semaphore

Khi thu thập dữ liệu API hoặc xử lý hàng nghìn mục hàng đợi, việc tạo ra hàng nghìn coroutine đồng thời không giới hạn sẽ làm quá tải các dịch vụ bên ngoài, làm cạn kiệt bộ mô tả tệp hoặc gây ra lỗi HTTP 429 Too Many Requests. Sử dụng asyncio.Semaphore để giới hạn đồng thời:

import asyncio
import aiohttp

class BoundedAPIScraper:
    def __init__(self, max_concurrent_requests: int = 25):
        self.semaphore = asyncio.Semaphore(max_concurrent_requests)
        self.session: aiohttp.ClientSession | None = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10))
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()

    async def fetch_item(self, item_id: int) -> dict:
        async with self.semaphore:
            # At most 25 requests will enter this block concurrently
            url = f"https://api.example.com/v1/items/{item_id}"
            assert self.session is not None
            async with self.session.get(url) as response:
                return await response.json()

    async def process_all(self, item_ids: list[int]) -> list[dict]:
        async with asyncio.TaskGroup() as tg:
            tasks = [tg.create_task(self.fetch_item(i)) for i in item_ids]
        return [t.result() for t in tasks]

Advertisement

Tăng thông lượng vòng lặp sự kiện: uvloop

Vòng lặp sự kiện Python mặc định (asyncio.SelectorEventLoop) được viết bằng Python thuần túy. Đối với các máy chủ Linux sản xuất, việc thay thế nó bằng uvloop (một bản thay thế trực tiếp được viết bằng Cython trên thư viện C libuv cơ bản của Node.js) sẽ tăng thông lượng I/O lên 2x đến 4x, đưa hiệu suất mạng Python ngang hàng trực tiếp với Node.js và Go.

uv add uvloop
import asyncio
import sys

# Configure uvloop as the default event loop policy on Unix
if sys.platform != "win32":
    import uvloop
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

async def main():
    print("Running on ultra-fast libuv event loop!")

if __name__ == "__main__":
    asyncio.run(main())

Bạn cũng có thể thích

Các câu hỏi thường gặp

Coroutine là một hàm được định nghĩa với async def mà nhường quyền điều khiển khi được await. Future là một đối tượng cấp thấp đại diện cho một kết quả cuối cùng chưa sẵn sàng. Task là một lớp con cụ thể của Future bao bọc một coroutine và lên lịch nó trên vòng lặp sự kiện để thực thi nền ngay lập tức.

TaskGroup triển khai đồng thời có cấu trúc: nếu bất kỳ tác vụ con nào thất bại với một ngoại lệ không được xử lý, TaskGroup tự động hủy tất cả các tác vụ anh em còn lại và đưa ra một ExceptionGroup. asyncio.gather cũ để lại các tác vụ anh em chạy như các coroutine mồ côi tách rời.

Không bao giờ chạy các vòng lặp nặng CPU hoặc các lệnh gọi đồng bộ chặn trực tiếp trong một hàm bất đồng bộ. Chuyển tải I/O đồng bộ hoặc công việc CPU nhanh bằng cách sử dụng asyncio.to_thread(). Đối với tính toán liên tục nặng (ví dụ: xử lý dữ liệu hoặc suy luận mô hình), hãy sử dụng ProcessPoolExecutor hoặc một hàng đợi tác vụ chuyên dụng như Celery.

Không. GIL chỉ hạn chế nhiều luồng bị giới hạn bởi CPU thực thi bytecode Python song song trên nhiều lõi. Đối với các khối lượng công việc mạng bị giới hạn bởi I/O (yêu cầu HTTP, đọc cơ sở dữ liệu, truyền trực tuyến WebSocket), luồng giải phóng GIL trong khi chờ trên socket mạng, cho phép asyncio xử lý hàng chục nghìn kết nối đồng thời một cách hiệu quả trên một lõi.


Kiểm tra kiến thức


Hướng dẫn & Tìm hiểu sâu liên quan

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement