小白也能懂的Python协程与asyncio指南
小白也能懂的Python协程与asyncio指南
一、从生活场景理解异步编程
1.1 买奶茶的两种方式
假设你要买三杯奶茶,每杯制作需要2分钟:
传统方式(同步):
def 买奶茶_同步():
    for _ in range(3):
        等待2分钟()  # 干等着不动
        拿奶茶()
# 总耗时:3×2=6分钟 ❌
聪明方式(异步):
async def 买奶茶_异步():
    订单列表 = [下单(), 下单(), 下单()]  # 同时下单
    await asyncio.gather(*订单列表)  # 边等边玩手机
# 总耗时:2分钟 ✅
1.2 协程就像外卖小哥
想象一个外卖小哥同时处理多个订单:
- 到A店取餐(等待时去B店)
- 途中接新订单(灵活调整路线)
- 送达后立即接下一单(不浪费时间)
这就是协程的工作方式!不需要多个小哥(线程),一个就能高效完成任务。
二、最简协程入门(附可运行代码)
2.1 Hello协程版
import asyncio
async def 打招呼(name):  # 关键1:async定义协程
    print(f"{name}开始做事")
    await asyncio.sleep(1)  # 关键2:遇到等待就挂起
    print(f"{name}事情做完啦")
async def 主任务():
    await asyncio.gather(
        打招呼("小明"),
        打招呼("小红")
    )
asyncio.run(主任务())  # 关键3:启动事件循环
输出结果:
小明开始做事
小红开始做事
(等待1秒)
小明事情做完啦
小红事情做完啦
2.2 执行过程图解

|700x615
三、必须掌握的3个核心概念
3.1 协程三要素
| 要素 | 说明 | 类比 | 
|---|---|---|
| async def | 声明协程函数 | 给外卖订单贴上"加急"标签 | 
| await | 暂停并让出控制权 | 小哥暂时离开去送其他订单 | 
| 事件循环 | 协调所有任务的调度员 | 外卖平台派单系统 | 
3.2 常见误区清单
- 错误:在普通函数中使用await
def 普通函数():
    await asyncio.sleep(1)  # 报错!
   - 错误:忘记创建任务
async def 错误示例():
    # 顺序执行,没有并发!
    await 任务1()
    await 任务2()
   - 正确做法:
async def 正确示例():
    task1 = asyncio.create_task(任务1())
    task2 = asyncio.create_task(任务2())
    await task1
    await task2
   四、手把手实战:下载多张图片
4.1 同步版本(龟速)
import requests
def 下载图片(url):
    print(f"开始下载 {url}")
    data = requests.get(url).content
    with open("图片.jpg", "wb") as f:
        f.write(data)
    print(f"下载完成 {url}")
def 主函数():
    urls = ["url1", "url2", "url3"]  # 假设3个图片地址
    for url in urls:
        下载图片(url)
# 总耗时:单张耗时 × 数量
4.2 异步版本(飞一般的感觉)
import aiohttp
async def 异步下载(url):
    async with aiohttp.ClientSession() as session:
        print(f"开始下载 {url}")
        async with session.get(url) as response:
            data = await response.read()
        with open(f"{url.split('/')[-1]}", "wb") as f:
            f.write(data)
        print(f"下载完成 {url}")
async def 主任务():
    urls = ["url1", "url2", "url3"]
    await asyncio.gather(*[异步下载(url) for url in urls])
asyncio.run(主任务())
4.3 性能对比
| 图片数量 | 同步耗时 | 异步耗时 | 速度提升 | 
|---|---|---|---|
| 10 | 20s | 2s | 10倍 | 
| 100 | 200s | 5s | 40倍 | 
五、常见问题解答
Q1:协程和多线程有什么区别?
| 特性 | 协程 | 多线程 | 
|---|---|---|
| 资源占用 | 一个线程搞定所有 | 每个线程需要独立资源 | 
| 切换方式 | 主动让出控制权 | 被系统强制切换 | 
| 适用场景 | 适合大量IO操作 | 适合计算密集型任务 | 
| 编程难度 | 需要理解异步语法 | 需要处理线程安全问题 | 
Q2:什么时候不能用协程?
- 需要大量CPU计算的场景(如视频转码)
- 使用不支持异步的库(比如传统的数据库驱动)
- 需要跨核并行计算(需结合多进程)
Q3:如何调试协程程序?
- 使用asyncio.run()作为入口
- 在协程内使用普通print语句
- 使用专业调试器: - import logging logging.basicConfig(level=logging.DEBUG)
六、最佳学习路线建议
6.1 新手三步走
- 先写同步代码理解业务流程
- 将耗时操作替换为async/await
- 用asyncio.gather实现并发
6.2 推荐练习项目
| 项目类型 | 实现功能 | 技能点 | 
|---|---|---|
| 天气查询器 | 同时查询多个城市天气 | 基础异步请求 | 
| 网页监控 | 定时检查多个网站状态 | 异步定时任务 | 
| 聊天机器人 | 同时处理多个用户消息 | 并发消息处理 | 
七、记住这5句话就够了
- async def:声明协程的起跑线
- await:遇到IO就举手暂停
- 事件循环:幕后总调度员
- create_task:把协程变成可执行任务
- asyncio.run():程序启动开关