Python 协程全解析:async/await、asyncio.run、协程 vs 多线程、I/O 密集首选协程
一、什么是协程?
协程(Coroutine)是一种轻量级的用户态线程,它由程序自己控制什么时候挂起、什么时候恢复,不依赖操作系统线程调度。
在 Python 中,协程是通过 async/await
实现的异步编程模型,依赖于事件循环(asyncio
)进行调度。
二、async/await 的作用是什么?
async def
:声明一个异步函数(协程函数),调用它返回一个协程对象。await
:在协程中挂起当前执行,等待另一个协程完成后再继续。
✅ 示例代码:
import asyncioasync def say_hello():await asyncio.sleep(1)print("Hello")asyncio.run(say_hello())
三、asyncio.run() 的作用是什么?
✅ 简洁定义:
asyncio.run() 是运行协程程序的推荐入口。
✅ 它做了什么?
- 创建事件循环
- 执行协程直到完成
- 自动关闭事件循环
- 返回协程的运行结果
✅ 示例:
import asyncioasync def main():await asyncio.sleep(1)return "任务完成"result = asyncio.run(main())
print(result)
四、async 函数必须包含 await 吗?
❌ 答案:不是必须,但通常推荐。
async def hello():print("Hi") # 没有 await 也合法asyncio.run(hello())
但这样写就失去了异步的意义,等同于普通函数。
五、协程调用协程是否必须 await?
✅ 是的!调用异步函数必须加 await 才会执行,否则只会返回一个协程对象,但不会运行。
❌ 错误示例:
async def inner():print("Inner")async def outer():inner() # ❌ 没有 await,不会执行!asyncio.run(outer())
✅ 正确写法:
async def inner():print("Inner")async def outer():await inner() # 👍 正确执行asyncio.run(outer())
六、协程和多线程的区别是什么?
对比项 | 协程(asyncio) | 多线程(threading) |
---|---|---|
并发模型 | 协作式 | 抢占式 |
本质 | 单线程 | 多线程 |
是否阻塞 | 非阻塞(主动让出) | 阻塞(由操作系统调度) |
适合场景 | I/O 密集 | I/O 或部分 CPU 密集 |
性能消耗 | 低(轻量级,切换开销小) | 高(线程切换与上下文开销大) |
是否需要加锁 | 通常不需要 | 需要(避免数据竞争) |
七、I/O 密集任务是否首选协程?
✅ 是的!
协程非常适合处理 I/O 密集场景,比如:
- 网络请求
- 数据库访问
- 文件读写
- WebSocket 通信
✅ 实例对比:并发请求 5 个网页
1. 同步方式(低效)
import requests
import timeurls = ['https://httpbin.org/delay/1'] * 5start = time.time()
for url in urls:requests.get(url)
print("同步耗时:", time.time() - start)## 2. 异步方式(高效)
```python
import asyncio
import aiohttp
import timeurls = ['https://httpbin.org/delay/1'] * 5async def fetch(session, url):async with session.get(url) as response:return await response.text()async def main():async with aiohttp.ClientSession() as session:tasks = [fetch(session, url) for url in urls]await asyncio.gather(*tasks)start = time.time()
asyncio.run(main())
print("异步耗时:", time.time() - start)
✅ 异步方式可大大缩短总耗时,支持高并发请求。
八、总结:协程的使用建议
场景 | 是否推荐协程 |
---|---|
网络请求(爬虫、API) | ✅ 强烈推荐 |
数据库访问(async 驱动) | ✅ 推荐 |
文件异步读写 | ✅ 推荐 |
高并发定时任务 | ✅ 推荐 |
CPU 密集型(图像处理等) | ❌ 不推荐,建议用多进程 |
📌 补充:为什么 Jupyter Notebook 中不能直接使用 asyncio.run()
?
在 Jupyter Notebook(或 IPython)中,事件循环已经自动运行,用于支持 await
表达式的直接运行。因此:
await some_coroutine()
# 在 Notebook 中是合法的,不需要放入 async def 函数中,也不需要 asyncio.run()。
❌ 如果你强行使用 asyncio.run() 会报错:
RuntimeError: asyncio.run() cannot be called from a running event loop
✅ 正确的解决方法(推荐):
方法 1:直接使用 await(Jupyter 专属)
import asyncioasync def say_hello():await asyncio.sleep(1)print("Hello")await say_hello() # ✅ Jupyter 中可以直接这样写
方法 2:使用 nest_asyncio 兼容运行
import nest_asyncio
import asyncionest_asyncio.apply() # 允许嵌套事件循环async def say_hello():await asyncio.sleep(1)print("Hello")asyncio.run(say_hello()) # ✅ 现在也能用了
✅ 总结:
环境 | 是否能用 asyncio.run() | 推荐方式 |
---|---|---|
普通 Python 脚本 | ✅ 可以 | asyncio.run() |
Jupyter Notebook | ❌ 默认不行 | 直接使用 await 或 nest_asyncio |