Python协程详解:async/await、asyncio.run应用,协程与多线程对比,I/O密集型任务首选协程之路
一、什么是协程?
协程(Coroutine)是一种轻量级的用户态线程,它由程序自己控制什么时候挂起、什么时候恢复,不依赖操作系统线程调度。
在 Python 中,协程是通过 async/await 实现的异步编程模型,依赖于事件循环(asyncio)进行调度。
二、async/await 的作用是什么?
async def:声明一个异步函数(协程函数),调用它返回一个协程对象。await:在协程中挂起当前执行,等待另一个协程完成后再继续。✅ 示例代码:
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("Hello")
asyncio.run(say_hello())
三、asyncio.run() 的作用是什么?
✅ 简洁定义:
asyncio.run() 是运行协程程序的推荐入口。
✅ 它做了什么?
✅ 示例:
import asyncio
async 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 密集场景,比如:
✅ 实例对比:并发请求 5 个网页
1. 同步方式(低效)
import requests
import time
urls = ['https://httpbin.org/delay/1'] * 5
start = time.time()
for url in urls:
requests.get(url)
print("同步耗时:", time.time() - start)
## 2. 异步方式(高效)
```python
import asyncio
import aiohttp
import time
urls = ['https://httpbin.org/delay/1'] * 5
async 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 asyncio
async def say_hello():
await asyncio.sleep(1)
print("Hello")
await say_hello() # ✅ Jupyter 中可以直接这样写
方法 2:使用 nest_asyncio 兼容运行
import nest_asyncio
import asyncio
nest_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 |
作者:Takoony