目录一、为什么需要异步编程1.1 同步 vs 异步1.2 阻塞 vs 非阻塞1.3 多线程、多进程与协程的对比二、协程是什么—— 可以“暂停”的函数2.1 生成器与协程的渊源2.2 async/await 语法糖三、asyncio 库实战 —— 让代码飞起来3.1 事件循环Event Loop—— 管家3.2 定义异步函数3.3 运行异步任务Task3.4 同时运行多个任务gather wait3.5 超时与取消四、实战案例异步爬取网页4.1 同步版本requests库4.2 异步版本aiohttp库4.3 性能对比小结五、常见坑点与注意事项5.1 不能在异步函数中使用 time.sleep5.2 注意线程安全问题5.3 异步库的生态5.4 记住协程函数必须被驱动六、总结一、为什么需要异步编程1.1 同步 vs 异步想象一下你中午点外卖同步模式你下单后就一直盯着手机看骑手到哪了啥事也不干直到外卖送到。这期间你浪费了大量时间在干等。异步模式你下单后该打游戏打游戏该写代码写代码。等外卖到了门铃一响通知你再去取餐。在程序世界里同步一个任务没做完CPU就一直等它什么也不干。这是Python默认的执行方式。异步一个任务遇到耗时操作比如读文件、请求网络就主动让出CPU让CPU去执行其他任务。等耗时操作完成了再回来继续执行。核心差异异步不是并行而是“遇到等待主动切换”。它让单线程也能实现高并发。1.2 阻塞 vs 非阻塞阻塞程序卡在那里什么都不能做。比如time.sleep(5)这5秒内CPU完全被占着空转。非阻塞调用后立刻返回不卡住。比如发起网络请求后先去干别的等数据回来了再处理。异步编程的目标把所有阻塞操作变成非阻塞榨干CPU的每一分潜力。1.3 多线程、多进程与协程的对比多线程、多进程它们和协程有什么区别方案优点缺点适合场景多线程共享内存切换开销较小有全局锁GIL线程不安全调试困难I/O密集型任务多进程真正并行利用多核资源占用大进程间通信麻烦CPU密集型任务协程极低的切换开销无锁写法像同步代码需要手动处理异步逻辑不能利用多核高并发I/O密集型一句话总结如果程序大部分时间在等待网络请求、数据库查询、文件读写协程是性价比最高的选择。二、协程是什么—— 可以“暂停”的函数2.1 生成器与协程的渊源在Python早期协程是通过生成器yield实现的。生成器可以暂停执行保存状态下次再恢复。这正是协程需要的能力。但用生成器写异步代码太反人类了到处是yield和send。于是Python 3.5正式引入了async/await语法让异步代码看起来就像普通的同步代码。2.2 async/await 语法糖async def定义一个协程函数。调用它不会立即执行而是返回一个协程对象。await等待一个可等待对象另一个协程、Future或Task。执行到这里程序会挂起当前协程去执行别的任务直到等待完成再回来。python import asyncio async def say_hello(): print(Hello) await asyncio.sleep(1) # 模拟耗时操作非阻塞等待 print(World) # 协程函数不会自动运行需要事件循环驱动 asyncio.run(say_hello())注意不能在普通函数里使用await也不能在协程函数里使用time.sleep那会真正阻塞整个线程要用await asyncio.sleep。三、asyncio 库实战 —— 让代码飞起来3.1 事件循环Event Loop—— 管家事件循环是异步编程的核心引擎。它就像一个永不停止的调度员负责1. 注册任务协程2. 当任务遇到await时把它挂起切换到另一个就绪的任务3. 监测哪些任务等待的事件已完成唤醒它们4. 所有任务完成后关闭循环我们通常用asyncio.run(main())来创建、运行并关闭事件循环。3.2 定义异步函数python async def fetch_data(url): print(f开始请求 {url}) await asyncio.sleep(2) # 模拟网络延迟 print(f完成请求 {url}) return f数据 from {url}3.3 运行异步任务Task单个协程asyncio.run(fetch_data(http://example.com))多个协程并发执行需要把协程包装成Task。python async def main(): # 创建任务同时开始执行 task1 asyncio.create_task(fetch_data(url1)) task2 asyncio.create_task(fetch_data(url2)) # 等待所有任务完成 result1 await task1 result2 await task2 print(result1, result2) asyncio.run(main()) # 输出 开始请求 url1 开始请求 url2 完成请求 url1 完成请求 url2 数据 from url1 数据 from url2注意两个请求几乎同时开始总耗时只有2秒而不是4秒这就是并发的威力。3.4 同时运行多个任务gather waitasyncio.gather()并发执行多个任务返回结果列表。asyncio.wait()更灵活可以设置超时、返回条件。python async def main(): tasks [fetch_data(url1), fetch_data(url2), fetch_data(url3)] results await asyncio.gather(*tasks) print(results) # 顺序对应任务顺序 asyncio.run(main())3.5 超时与取消用asyncio.wait_for给任务设置超时python async def main(): try: result await asyncio.wait_for(fetch_data(slow_url), timeout1) except asyncio.TimeoutError: print(请求超时了)四、实战案例异步爬取网页我们来对比同步和异步爬虫的性能差距。假设要爬取10个网页。4.1 同步版本requests库python import requests import time urls [https://httpbin.org/delay/1] * 10 # 每个请求延迟1秒 def sync_fetch(): start time.time() for url in urls: response requests.get(url) print(f完成 {url}, 状态码 {response.status_code}) print(f同步耗时: {time.time() - start:.2f}秒) sync_fetch() # 输出: 同步耗时: 约10秒4.2 异步版本aiohttp库python import aiohttp import asyncio import time async def fetch_async(session, url): async with session.get(url) as response: return await response.text() async def main(): start time.time() async with aiohttp.ClientSession() as session: tasks [fetch_async(session, url) for url in urls] await asyncio.gather(*tasks) print(f异步耗时: {time.time() - start:.2f}秒) asyncio.run(main()) # 输出: 异步耗时: 约1秒性能提升10倍同步爬10个网页要等10秒异步只用了1秒多加上一点额外开销。这还只是1秒延迟的例子如果每个请求耗时5秒差距会更夸张。4.3 性能对比小结同步总时间 单个请求时间 × 请求数量异步总时间 ≈ 最慢的那个请求时间并发足够大时注意异步不是万能的。如果任务是CPU密集型的比如计算圆周率异步没用这时用多进程。五、常见坑点与注意事项5.1 不能在异步函数中使用 time.sleeptime.sleep会阻塞整个线程导致事件循环无法切换任务。一定要用await asyncio.sleep。错误python async def bad(): time.sleep(1) # 阻塞了正确python async def good(): await asyncio.sleep(1)5.2 注意线程安全问题asyncio是单线程内的并发通常不需要锁。但如果混用多线程例如用run_in_executor要小心共享数据的同步。5.3 异步库的生态很多传统库如requests、openpyxl不支持异步。需要使用对应的异步版本同步库异步替代requestsaiohttp / httpxsqlalchemydatabases asyncpg / aiomysqlfile openaiofilesredisaioredis5.4 记住协程函数必须被驱动如果你定义了一个async def函数但只是调用它而不await或asyncio.run它会返回一个协程对象什么都不会发生。python async def test(): print(不会打印) test() # 只是得到一个协程对象没有输出六、总结核心要点回顾1. 异步编程适合I/O密集型任务能极大提升并发能力。2. 协程是可暂停的函数async/await是它的语法糖。3. 事件循环是异步的引擎负责调度任务。4. 用asyncio.create_task、gather、wait来并发执行多个任务。5. 实战中替换阻塞库为异步版本性能提升立竿见影。如果觉得这篇文章对你有帮助可以点赞、收藏、关注