Skip to content

Python异步编程:真假异步完全解析

一、异步编程的本质

异步编程的核心目标是在IO等待时释放CPU去做其他事情,从而提升系统吞吐量。但Python中很容易写出"假异步"代码,表面用了async/await语法,实际仍是同步执行。

二、真假异步对比表

特征真异步假异步
语法表现使用async/await同样使用async/await
执行方式IO操作并行重叠顺序等待每个IO完成
CPU利用率IO等待时执行其他任务IO等待时CPU空闲
耗时特性总耗时≈最长单个任务耗时总耗时=所有任务耗时之和
代码结构有任务创建和收集机制直接在循环中await

三、典型代码示例

假异步案例

python
async def fake_async_demo(urls):
    """表面异步实际同步的爬虫"""
    results = []
    for url in urls:
        # 每次请求都会阻塞循环
        response = await fetch(url)  
        results.append(response)
    return results

真异步实现

python
async def true_async_demo(urls):
    """真正的异步爬虫"""
    tasks = []
    for url in urls:
        # 创建任务但不立即等待
        task = asyncio.create_task(fetch(url))
        tasks.append(task)
    
    # 统一等待所有任务
    return await asyncio.gather(*tasks)

四、性能影响实测

假设处理10个URL,每个请求需要500ms:

方式代码特征总耗时CPU利用率
同步版普通requests.get~5s
假异步循环内直接await~5s
真异步create_task+gather~0.5s

五、识别假异步的技巧

  1. await位置检测法

    • ❌ 危险信号:在for/while循环内直接await
    • ✅ 正确模式:先create_task再统一gather
  2. 时间测量法

    python
    start = time.time()
    await your_async_function()
    print(f"耗时:{time.time()-start:.2f}s")

    真异步耗时不应随任务数线性增长

  3. 并发流观察法

    • 真异步会同时发起多个网络连接
    • 可用tcpdump或Wireshark抓包验证

六、常见误区与陷阱

误区1:认为用了async就是异步

python
# 错误认知:以为用了await就是异步
async def misleading():
    for i in range(10):
        await asyncio.sleep(1)  # 仍然是顺序执行!

误区2:忽视异常处理

python
# 危险代码:一个任务失败会导致整个gather失败
await asyncio.gather(task1, task2)

# 正确做法:
await asyncio.gather(task1, task2, return_exceptions=True)

误区3:滥用无限并发

python
# 可能引发DDOS的代码
tasks = [fetch(url) for url in unlimited_list]
await asyncio.gather(*tasks)

# 应使用信号量控制
sem = asyncio.Semaphore(10)
async def limited_fetch(url):
    async with sem:
        return await fetch(url)

七、最佳实践建议

  1. 模式选择

    • IO密集型:真异步(asyncio)
    • CPU密集型:多进程(ProcessPoolExecutor)
    • 混合型:asyncio.to_thread+异步IO
  2. 结构设计

    python
    async def robust_worker(items):
        sem = asyncio.Semaphore(100)  # 控制并发量
        
        async def process(item):
            async with sem:
                try:
                    return await real_work(item)
                except Exception as e:
                    logger.error(f"处理失败 {item}: {str(e)}")
                    return None
        
        tasks = [process(i) for i in items]
        return await asyncio.gather(*tasks, return_exceptions=True)
  3. 调试技巧

    • 使用asyncio.debug=True启用调试模式
    • 通过loop.slow_callback_duration检测阻塞调用
    • aiomonitor进行实时监控

八、思考题

以下代码是真异步还是假异步?

python
async def quiz():
    coros = [download(url) for url in urls]
    for coro in coros:
        await coro

答案:假异步!虽然创建了多个协程对象,但仍然在循环中顺序await。正确做法应使用asyncio.gather

✨ 网站运行时间: 3年11月15天 ❤️ 道阻且长,行则将至 - 微信号: heikedreamer