下面学习的内容涉及源代码: https://blog.csdn.net/andylin02/article/details/159652101?spm=1001.2014.3001.5502

一、为什么需要异步?——从阻塞 I/O 到并发模型

1.1 同步 vs 异步 I/O

  • 同步 I/O:发起请求 → 线程阻塞 → 等待完成 → 继续执行
    (如 requests.get()
  • 异步 I/O:发起请求 → 立即返回 → 执行其他任务 → 数据就绪时回调处理
    (如 aiohttp.get()

1.2 异步的优势

  • 高并发:单线程可同时处理数千个 I/O 操作(如 Web 请求、数据库查询)
  • 低资源消耗:无需创建大量线程(避免上下文切换开销)
  • 适合 I/O 密集型场景:网络、文件、数据库等

⚠️ 注意:异步不能加速 CPU 密集型任务(此时用多进程)


二、核心概念解析

2.1 协程(Coroutine)

  • 定义:可暂停和恢复的函数
  • 声明方式
    async def fetch(url):
        return await aiohttp.get(url)  # 这里只是示例
    
  • 调用 fetch("...") 返回一个 协程对象(未执行!)

2.2 事件循环(Event Loop)

  • 作用:调度协程、监听 I/O 事件、执行回调
  • 获取当前 loop:
    loop = asyncio.get_event_loop()
    # 或 (Python 3.7+)
    loop = asyncio.get_running_loop()
    

2.3 Future / Task

对象说明
Future表示“未来某个时刻会完成的操作”(底层抽象)
Task对协程的封装,自动调度执行(推荐使用)
task = asyncio.create_task(fetch("https://example.com"))
result = await task

三、async/await 语法详解

3.1 基本结构

import asyncio

async def main():
    print("Start")
    await asyncio.sleep(1)  # 模拟异步 I/O
    print("End")

# 启动(Python 3.7+)
asyncio.run(main())

3.2 关键规则

  • await 只能在 async def 中使用
  • await 后必须是 awaitable 对象(协程、Task、Future)
  • 不要在异步函数中调用阻塞函数(如 time.sleeprequests.get
❌ 错误示例:

async def bad():
    time.sleep(2) # 阻塞整个事件循环!
✅ 正确做法:

async def good():
    await asyncio.sleep(2) # 非阻塞

四、并发执行模式

4.1 顺序执行(无并发)

async def main():
    await fetch(url1)
    await fetch(url2)  # 等第一个完成后才开始

4.2 并发执行(推荐)

方式1:asyncio.gather()

async def main():
    results = await asyncio.gather(
        fetch(url1),
        fetch(url2),
        fetch(url3)
    )
  • 特点:所有任务同时启动,等待全部完成
  • 异常处理:默认一个失败则全部取消(可用 return_exceptions=True
方式2:asyncio.create_task() + await
async def main():
    t1 = asyncio.create_task(fetch(url1))
    t2 = asyncio.create_task(fetch(url2))
    r1 = await t1
    r2 = await t2
  • 特点:更灵活,可分批等待
方式3:限制并发数(防爆)
from asyncio import Semaphore

sem = Semaphore(10)  # 最多10个并发

async def safe_fetch(url):
    async with sem:
        return await fetch(url)

# 提交1000个任务
tasks = [safe_fetch(url) for url in urls]
results = await asyncio.gather(*tasks)

五、异步上下文管理器与迭代器

5.1 异步上下文管理器(async with

用于异步资源管理(如数据库连接、HTTP 会话):

async def fetch_data():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com") as resp:
            return await resp.json()

5.2 异步迭代器(async for

用于异步生成数据流:

async def async_range(n):
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

async def main():
    async for x in async_range(5):
        print(x)

六、与同步代码集成

6.1 在异步中运行阻塞函数

使用 loop.run_in_executor() 将阻塞任务交给线程池:

def blocking_io():
    with open('/tmp/file.txt', 'rb') as f:
        return f.read()

async def main():
    loop = asyncio.get_running_loop()
    data = await loop.run_in_executor(None, blocking_io)
    print(data)

💡 None 表示使用默认线程池(ThreadPoolExecutor

6.2 从同步代码调用异步函数

# 不推荐(仅用于顶层)
asyncio.run(async_func())

# 或在已有 loop 中(如 Jupyter)
await async_func()

⚠️ 不要混用 asyncio.run() 多次(会创建多个 loop)


七、常用异步库推荐

领域说明
HTTP 客户端aiohttp支持客户端和服务端
HTTP 客户端httpx更现代,支持 HTTP/2
数据库asyncpg (PostgreSQL)高性能
数据库aiomysql / aiosqliteMySQL / SQLite
Web 框架FastAPI基于 Starlette,原生异步
Web 框架QuartFlask 的异步版
爬虫scrapy (via scrapy-poet)或自研 aiohttp + asyncio

八、调试与测试

8.1 调试技巧

  • 启用调试模式:
asyncio.run(main(), debug=True)

  • 查看未完成的 Task:
tasks = asyncio.all_tasks()
for t in tasks:
    print(t)

8.2 单元测试

使用 pytest-asyncio

import pytest

@pytest.mark.asyncio
async def test_fetch():
    result = await fetch("https://example.com")
    assert result is not None

九、常见陷阱与最佳实践

❌ 陷阱1:忘记 await

async def main():
    fetch(url)  # 协程对象被创建但未执行!

✅ 正确:await fetch(url)

❌ 陷阱2:在异步函数中使用阻塞 I/O

async def handler():
    requests.get(url)  # 阻塞整个事件循环!

✅ 正确:用 aiohttp 或 run_in_executor

❌ 陷阱3:滥用 asyncio.run()

  • 在已有事件循环中(如 Jupyter、Web 框架)不要调用 asyncio.run()
  • 改为直接 await

✅ 最佳实践

  1. I/O 操作必须异步化(网络、文件、DB)
  2. CPU 密集型任务交给 ProcessPoolExecutor
  3. 限制并发数(防止打爆目标服务器或本地资源)
  4. 使用类型注解async def f() -> str:
  5. 优雅关闭资源async with

十、底层原理简析(进阶)

10.1 事件循环如何工作?

  • 基于 Reactor 模式
  • 使用系统调用(如 Linux 的 epoll、macOS 的 kqueue)监听 I/O 事件
  • 当 socket 可读/可写时,回调对应协程继续执行

10.2 协程 vs 线程

特性协程线程
切换开销极低(用户态)高(内核态)
内存占用KB 级MB 级
并发能力数万+数百(受 GIL 和内存限制)
编程复杂度需理解异步模型需处理锁和竞态

十一、学习路径建议

  1. 入门:掌握 async/awaitasyncio.run()gather
  2. 实战:写一个异步爬虫或 API 客户端
  3. 进阶:理解事件循环、Task 调度、异常传播
  4. 精通:阅读 asyncio 源码、实现自定义异步协议

十二、推荐阅读

  • 官方文档:https://docs.python.org/3/library/asyncio.html
  • 书籍:《Python 异步编程实战》(中文)、《Using Asyncio in Python》(Caleb Hattingh)
  • 视频:David Beazley 的 “Async IO in Python” 演讲(YouTube)

✅ 总结

异步编程不是银弹,而是解决 I/O 并发问题的利器。

  • 用对场景(I/O 密集型)→ 性能飞跃
  • 用错场景(CPU 密集型)→ 得不偿失
  • 忽略规则(阻塞调用、忘记 await)→ 程序退化为同步

掌握 async/await,你就能用 Python 写出媲美 Node.js、Go 的高并发服务!


📌 动手建议
立即尝试将一个同步爬虫改造成异步版本,对比 QPS 提升效果!

更多推荐