Python并发编程学习 day3
2025-06-30 15:12 第二个卿老师 阅读(45) 评论(0) 收藏 举报Day 3:Python 协程编程基础
一、核心概念
- 异步IO(Async IO):一种编程范式(模型),程序可能不会等待一个执行步骤完成就转到下一个执行步骤
异步IO是一种并发编程风格,但不是并行,它是一种单进程、单线程的设计,使用了协作式多任务处理
异步 IO 需要较长的等待时间,否则函数会被阻塞,并允许其他函数在停机时间内运行
-
协程(Coroutine):
- 本质上是可以在执行过程中暂停并切换的函数
- 用于构建非阻塞 I/O 编程模型(单线程高并发)
-
事件循环(Event Loop):
- 负责调度和执行所有异步任务,由
asyncio提供
- 负责调度和执行所有异步任务,由
-
关键字语法:
async def定义协程函数await挂起等待另一个协程完成
示例:
import asyncio async def hello(): print("Hello") await asyncio.sleep(1) print("World") asyncio.run(hello())
协程属于可等待对象,另外task与future也属于可等待对象,task是协程高层级的封装,而future是低层级可等待对象
二、asyncio 核心 API(Python 3.10)
1. 启动入口
asyncio.run(main()) # 通常用来运行最高层级的入口点 "main()" 函数
2. 创建任务:
asyncio.create_task():将协程包装为任务并立即调度执行
async def worker(name):
print(f"Start {name}")
await asyncio.sleep(1)
print(f"End {name}")
async def main():
task1 = asyncio.create_task(worker("A"))
task2 = asyncio.create_task(worker("B"))
await task1
await task2
主要用于控制单个任务,任务会立即启动并且需要单独await task来获取结果,3.11版本及以后推荐使用asyncio.TaskGroup.create_task() 来代替
3. 并发调度任务
asyncio.gather(*tasks,return_exceptions=False):自动把协程作为一个任务调度,返回所有可等待对象都成功完成的返回值列表
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({number}), currently i={i}...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
return f
async def main():
# 将三个调用 *并发地* 加入计划任务:
L = await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
print(L)
asyncio.run(main())
主要用于批量管理任务组,启动任务并等待所有完成,自动获取所有任务结果。默认情况下,一个任务失败会导致整体失败(除非 return_exceptions=True)
asyncio.wait(*tasks, *, timeout=None, return_when=ALL_COMPLETED):监控一组任务的完成状态,可设置完成条件(如:FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED)
async def task(n):
await asyncio.sleep(n)
return f"Task-{n}"
async def main():
tasks = [asyncio.create_task(task(i)) for i in [3, 1, 2]]
# 等待第一个任务完成
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED
)
print(f"第一个完成: {next(iter(done)).result()}")
print(f"仍在运行: {len(pending)}")
asyncio.run(main())
asyncio.wait()返回已完成和未完成的任务集合,可设置超时但不自动取消任务
4. 协程与超时控制
asyncio.wait_for(task, timeout):为单个异步操作添加超时控制,防止无限期阻塞
async def long_operation():
await asyncio.sleep(5)
return "结果"
async def main():
try:
# 设置3秒超时
result = await asyncio.wait_for(long_operation(), timeout=3)
except asyncio.TimeoutError:
print("操作超时!")
else:
print(f"结果: {result}")
asyncio.run(main())
asyncio.wait_for()与asyncio.wait()不同,前者专门处理单个任务且超时自动取消任务
5. 异步上下文管理器 & 异步迭代器
async with
语法:async with expr as var,主要用于管理异步资源的获取/释放,进入/退出代码块时挂起,常用场景包括网络连接、数据库连接、文件操作async for
语法:async for item in async_iterable,主要用于异步遍历异步可迭代对象,每次迭代时挂起,常用场景包括流式数据处理、实时消息处理、分页API
三、协程适用场景
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| I/O密集型任务 | asyncio 协程 |
单线程高并发、低开销 |
| 网络请求 / 爬虫 | aiohttp + asyncio | 高并发非阻塞请求 |
| 高频文件操作 | aiofiles | 异步 I/O 释放控制权 |
四、协程编程注意事项
- 阻塞调用禁止:不要在协程中使用
time.sleep()、requests.get()等阻塞操作 - 使用 aio 版本库:如
aiohttp、aiomysql、aiofiles - 异常处理机制:注意
await表达式的异常抛出 & 捕获方式
五、练习任务:高并发异步请求模拟
目标:使用
asyncio + aiohttp模拟发起多个异步 HTTP 请求并收集结果
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["https://httpbin.org/delay/1" for _ in range(10)]
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
results = await asyncio.gather(*tasks)
print(f"共返回 {len(results)} 条数据")
if __name__ == '__main__':
asyncio.run(main())
六、今日思考题
- 为什么协程可以提升 I/O 并发性能,但不能加速 CPU 计算?
答:因为协程的原理是在等待IO时,CPU存在空闲状态,就可以运行其他任务,而CPU计算会一直占有CPU,就没有空闲状态 create_task与gather的区别是什么?
答:create_task操作单个任务而gather操作多个任务,前者运行时立即启动任务,不阻塞当前流程,但需要手动等待获取结果,后者运行时启动任务并等待所有任务完成,自动返回所有结果的有序列表async with和普通with有什么本质差别?
答:从两者的执行模型和适用场景来说:普通 with 是同步阻塞的,适用于快速完成的操作,async with 是异步非阻塞的,适用于I/O密集型操作,async with 通过挂起协程而非阻塞线程来实现高效并发
浙公网安备 33010602011771号