一、什么是协程?
协程(Coroutine)是一种轻量级的用户态线程,它由程序自己控制什么时候挂起、什么时候恢复,不依赖操作系统线程调度。
在 Python 中,协程是通过 async/await
实现的异步编程模型,依赖于事件循环(asyncio
)进行调度。
二、async/await 的作用是什么?
async def
:声明一个异步函数(协程函数),调用它返回一个协程对象。await
:在协程中挂起当前执行,等待另一个协程完成后再继续。
✅ 协程的设计初衷是“并发多个任务”
协程是为了解决 I/O 密集型的并发问题而设计的。它的优势体现在:
- 多个任务同时等待 I/O(如文件、网络、数据库)时,
- 不需要多个线程,也能做到“同时处理多个请求”。
✅ 示例代码1(无意义):
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("Hello")
asyncio.run(say_hello())
# 只有一个任务,await 的时候事件循环没有其他任务可以调度。
# 所以这个“让出控制权”其实没有带来任何并发优势。
示例代码2(无意义)
import asyncio
async def task(name, delay):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
async def main():
# 直接调用协程函数 - 错误!
await task("任务1", 2)
await task("任务2", 1)
await task("任务3", 0.5)
asyncio.run(main())
"""
输出:
任务1 开始
任务1 完成
任务2 开始
任务2 完成
任务3 开始
任务3 完成
"""
示例代码3-正确
import asyncio
async def main():
# 创建任务但不等待
task1 = asyncio.create_task(task("任务1", 2))
task2 = asyncio.create_task(task("任务2", 1))
task3 = asyncio.create_task(task("任务3", 0.5))
# 等待所有任务完成
await task1
await task2
await task3
asyncio.run(main())
"""
输出结果:
任务1 开始
任务2 开始
任务3 开始
任务3 完成
任务2 完成
任务1 完成
"""
示例代码4-正确
import asyncio
async def main():
# 一次性收集并执行所有任务
await asyncio.gather(
task("任务1", 2),
task("任务2", 1),
task("任务3", 0.5)
)
asyncio.run(main())
"""
输出结果:
任务1 开始
任务2 开始
任务3 开始
任务3 完成
任务2 完成
任务1 完成
"""
三、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())
import asyncio
async def task_one():
print("任务 1 开始")
await asyncio.sleep(1) # 模拟 I/O 操作
print("任务 1 完成")
async def task_two():
print("任务 2 开始")
await asyncio.sleep(0.5) # 较短的等待
print("任务 2 完成")
async def main():
# 错误方式:顺序执行
print("\n=== 顺序执行(错误方式)===")
await task_one()
await task_two() # 在 task_one 完成后才开始
# 正确方式:并发执行
print("\n=== 并发执行(正确方式)===")
task1 = asyncio.create_task(task_one())
task2 = asyncio.create_task(task_two())
await task1 # 等待但不阻塞
await task2
asyncio.run(main())
执行结果:
=== 顺序执行(错误方式)===
任务 1 开始
任务 1 完成
任务 2 开始
任务 2 完成
=== 并发执行(正确方式)===
任务 1 开始
任务 2 开始
任务 2 完成
任务 1 完成
六、协程和多线程的区别是什么?
对比项 | 协程(asyncio) | 多线程(threading) |
---|---|---|
并发模型 | 协作式 | 抢占式 |
本质 | 单线程 | 多线程 |
是否阻塞 | 非阻塞(主动让出) | 阻塞(由操作系统调度) |
适合场景 | I/O 密集 | I/O 或部分 CPU 密集 |
性能消耗 | 低(轻量级,切换开销小) | 高(线程切换与上下文开销大) |
是否需要加锁 | 通常不需要 | 需要(避免数据竞争) |
七、I/O 密集任务是否首选协程?
✅ 是的!
协程非常适合处理 I/O 密集场景,比如:
- 网络请求
- 数据库访问
- 文件读写
- WebSocket 通信
✅ 实例对比:并发请求 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 |