1. 什么是协程?
协程(Coroutine)是Python中一种特殊的函数,它可以在执行过程中暂停,并在需要时恢复执行。与普通函数不同,协程可以在多个入口点暂停和恢复,这使得它非常适合处理I/O密集型任务和并发编程。
简单来说,协程就像是"可以暂停的函数",它允许我们在函数执行到一半时挂起,去做其他事情,然后再回来继续执行。
2. 协程的发展历程
2.1 生成器(Generator)时代
Python 2.5引入了生成器表达式和yield语句,这是协程的雏形。通过yield,函数可以暂停执行并返回一个值,下次调用时从暂停处继续执行。
python
def simple_generator():
print("开始")
yield 1
print("继续")
yield 2
print("结束")
gen = simple_generator()
print(next(gen)) # 输出:开始,然后 1
print(next(gen)) # 输出:继续,然后 2
2.2 基于生成器的协程
Python 3.3引入了yield from语法,使得生成器可以委托给其他生成器,这为协程提供了更好的支持。
python
def generator_a():
yield from [1, 2, 3]
def generator_b():
yield from generator_a()
yield from [4, 5, 6]
for value in generator_b():
print(value) # 输出:1, 2, 3, 4, 5, 6
2.3 async/await时代
Python 3.5引入了async和await关键字,这是现代协程的标准写法。async def用于定义协程函数,await用于等待协程执行完成。
python
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
# 运行协程
asyncio.run(say_hello())
3. 协程的核心概念
3.1 协程函数 vs 协程对象
- 协程函数 :使用
async def定义的函数 - 协程对象:调用协程函数返回的对象
python
async def my_coroutine():
return 42
# my_coroutine 是协程函数
coro = my_coroutine() # coro 是协程对象
3.2 事件循环(Event Loop)
事件循环是异步编程的核心,它负责调度和执行协程。在Python中,asyncio模块提供了事件循环的实现。
python
import asyncio
async def main():
print("开始")
await asyncio.sleep(1)
print("结束")
# 创建事件循环并运行协程
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
3.3 可等待对象(Awaitable)
可等待对象是可以在await表达式中使用的对象,包括:
- 协程(Coroutine)
- 任务(Task)
- Future对象
4. 使用async/await编写协程
4.1 基本用法
python
import asyncio
async def fetch_data():
print("开始获取数据")
await asyncio.sleep(2) # 模拟网络请求
print("数据获取完成")
return {"data": "some data"}
async def process_data():
print("开始处理数据")
await asyncio.sleep(1) # 模拟数据处理
print("数据处理完成")
return "processed"
async def main():
# 顺序执行
data = await fetch_data()
result = await process_data()
print(f"数据: {data}, 结果: {result}")
asyncio.run(main())
4.2 并发执行多个协程
python
import asyncio
async def task(name, seconds):
print(f"任务 {name} 开始")
await asyncio.sleep(seconds)
print(f"任务 {name} 完成")
return f"任务 {name} 的结果"
async def main():
# 使用asyncio.gather并发执行多个协程
results = await asyncio.gather(
task("A", 2),
task("B", 1),
task("C", 3)
)
print(f"所有任务完成: {results}")
asyncio.run(main())
4.3 创建任务(Task)
任务是对协程的进一步封装,它可以在事件循环中调度执行。
python
import asyncio
async def long_running_task():
print("长时间任务开始")
await asyncio.sleep(5)
print("长时间任务完成")
return "任务结果"
async def main():
# 创建任务
task1 = asyncio.create_task(long_running_task())
# 在等待任务完成的同时可以做其他事情
print("主程序继续执行...")
await asyncio.sleep(1)
print("检查任务状态...")
# 等待任务完成
result = await task1
print(f"任务结果: {result}")
asyncio.run(main())
5. 协程的实际应用场景
5.1 网络请求
python
import aiohttp
import asyncio
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
urls = [
"https://httpbin.org/get",
"https://httpbin.org/ip",
"https://httpbin.org/user-agent"
]
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, content in zip(urls, results):
print(f"{url}: {len(content)} 字符")
asyncio.run(main())
5.2 数据库操作
python
import asyncio
import asyncpg
async def database_operations():
# 连接数据库
conn = await asyncpg.connect(
user='user',
password='password',
database='database',
host='localhost'
)
# 执行查询
rows = await conn.fetch('SELECT * FROM users WHERE age > $1', 18)
for row in rows:
print(f"用户: {row['name']}, 年龄: {row['age']}")
# 关闭连接
await conn.close()
asyncio.run(database_operations())
5.3 Web服务器
python
from aiohttp import web
import asyncio
async def handle_request(request):
name = request.match_info.get('name', "World")
text = f"Hello, {name}!"
return web.Response(text=text)
async def background_task(app):
while True:
print("后台任务运行中...")
await asyncio.sleep(5)
async def init_app():
app = web.Application()
app.router.add_get('/', handle_request)
app.router.add_get('/{name}', handle_request)
# 启动后台任务
app.on_startup.append(lambda app: asyncio.create_task(background_task(app)))
return app
web.run_app(init_app(), port=8080)
6. 协程的注意事项和最佳实践
6.1 避免阻塞操作
在协程中执行阻塞操作(如time.sleep()、同步文件IO等)会阻塞整个事件循环。
python
# ❌ 错误示例:在协程中使用同步sleep
async def bad_example():
import time
time.sleep(5) # 这会阻塞事件循环!
# ✅ 正确示例:使用异步sleep
async def good_example():
await asyncio.sleep(5) # 这不会阻塞事件循环
6.2 错误处理
python
import asyncio
async def risky_operation():
await asyncio.sleep(1)
raise ValueError("出错了!")
async def main():
try:
await risky_operation()
except ValueError as e:
print(f"捕获到错误: {e}")
# 或者使用asyncio.gather的return_exceptions参数
results = await asyncio.gather(
risky_operation(),
return_exceptions=True
)
for result in results:
if isinstance(result, Exception):
print(f"任务出错: {result}")
else:
print(f"任务成功: {result}")
asyncio.run(main())
6.3 超时控制
python
import asyncio
async def slow_operation():
await asyncio.sleep(10)
return "完成"
async def main():
try:
# 设置5秒超时
result = await asyncio.wait_for(slow_operation(), timeout=5)
print(f"结果: {result}")
except asyncio.TimeoutError:
print("操作超时!")
asyncio.run(main())
7. 协程与线程、进程的对比
| 特性 | 协程 | 线程 | 进程 |
|---|---|---|---|
| 创建开销 | 很小 | 中等 | 很大 |
| 切换开销 | 很小 | 中等 | 很大 |
| 内存占用 | 很小 | 中等 | 很大 |
| 并发类型 | 协作式 | 抢占式 | 抢占式 |
| 数据共享 | 共享内存 | 共享内存 | 需要IPC |
| 适用场景 | I/O密集型 | I/O密集型、CPU密集型 | CPU密集型 |
8. 总结
Python协程是现代异步编程的核心,它通过async/await语法提供了简洁高效的并发编程方式。协程特别适合处理I/O密集型任务,如网络请求、数据库操作等。
关键要点:
- 使用
async def定义协程函数,使用await等待协程完成 - 事件循环是协程调度的核心
- 使用
asyncio.gather()并发执行多个协程 - 避免在协程中使用阻塞操作
- 合理处理协程中的错误和超时
随着Python异步生态的不断完善,协程已经成为构建高性能网络应用的重要工具。掌握协程的使用,能够让你编写出更高效、更易维护的并发代码。