这一节了解一下Python中的asyncio,asyncio是Python 标准库提供的异步I/O框架,是实现协程、异步任务、事件循环的核心库,用来编写单线程并发IO代码。简单总结如下:
API:
- async def,异步函数定义关键字,用来声明一个协程函数。定义后调用不会立刻执行函数内部代码,而是返回一个协程对象,必须放入事件循环运行。
python
async def func():
pass
coro_obj = func()
- await,协程内的等待关键字,只能写在 async def 函数内部。让出事件循环控制权,暂停当前协程,等待可等待对象完成,拿到返回结果。
python
async def demo():
res = await some_awaitable()
return res
- asyncio.run(),异步程序入口主函数,Python3.7+提供。自动创建事件循环、运行协程、关闭循环,是日常写异步代码最推荐的入口。
python
async def main():
pass
asyncio.run(main())
- asyncio.create_task(),创建异步任务的API。把协程包装成Task对象,自动提交到事件循环并发调度,任务创建后就开始调度执行。
python
async def work():
pass
task = asyncio.create_task(work())
result = await task
- asyncio.gather(),批量并发调度多个可等待对象。同时运行多个协程/任务,收集所有返回值,返回结果列表;一个任务异常默认直接抛出。
python
res_list = await asyncio.gather(coro1(), coro2(), coro3())
- asyncio.gather(return_exceptions=True),gather的扩展参数用法。捕获子任务异常,把异常对象当作结果放入返回列表,不会中断整体gather等待。
python
res_list = await asyncio.gather(coro1(), coro2(), return_exceptions=True)
- asyncio.wait(),等待一组可等待对象完成,比gather更灵活。返回完成集合和未完成集合,支持控制等待条件
python
done, pending = await asyncio.wait([task1, task2])
- asyncio.FIRST_COMPLETED,asyncio.wait 的常量参数。传给wait,只要任意一个任务完成就立刻返回,不再等待剩下任务。
python
done, pending = await asyncio.wait([t1,t2], return_when=asyncio.FIRST_COMPLETED)
- asyncio.FIRST_EXCEPTION,asyncio.wait 的常量参数。传给wait,当任意任务抛出异常或者全部完成时返回,优先触发异常。
python
done, pending = await asyncio.wait([t1,t2], return_when=asyncio.FIRST_EXCEPTION)
- asyncio.ALL_COMPLETED,asyncio.wait 默认常量参数。等待集合里所有任务全部执行完毕才返回。
python
done, pending = await asyncio.wait([t1,t2], return_when=asyncio.ALL_COMPLETED)
- asyncio.wait_for(),带超时的等待API。等待一个可等待对象,设置最大超时时间,超时没完成抛出 asyncio.TimeoutError。
python
try:
res = await asyncio.wait_for(coro(), timeout=3)
except asyncio.TimeoutError:
print("超时")
- asyncio.sleep(),异步休眠函数,不会阻塞事件循环。让出控制权,异步等待指定秒数,和 time.sleep()本质区别是不阻塞其他协程。
python
await asyncio.sleep(1)
- asyncio.Lock,异步锁对象,协程版本互斥锁。保证同一时刻只有一个协程进入临界区,解决协程之间共享资源竞争问题,不可重入。
python
lock = asyncio.Lock()
async def work():
async with lock:
# 临界区代码
pass
- asyncio.Semaphore,异步信号量。控制并发数量,限制同时运行的协程上限,比如限制爬虫并发数。
python
sem = asyncio.Semaphore(5)
async def req():
async with sem:
await fetch()
- asyncio.BoundedSemaphore,有界信号量,Semaphore子类。和Semaphore功能一致,额外校验释放次数不能超过初始值,防止错误release。
python
sem = asyncio.BoundedSemaphore(3)
async def task():
async with sem:
pass
- asyncio.Event,异步事件通知对象。协程之间信号通知,一个协程set标记,其他协程await event.wait()阻塞等待信号。
python
event = asyncio.Event()
async def waiter():
await event.wait()
print("收到信号")
async def setter():
event.set()
- event.set(),Event对象方法。把事件标记置为True,唤醒所有正在await event.wait()的协程。
python
event.set()
- event.clear(),Event对象方法。重置事件标记为False,后续wait会再次阻塞。
python
event.clear()
- event.is_set(),Event对象方法。查询当前事件标记状态,返回布尔值。
python
if event.is_set():
pass
- asyncio.Condition,异步条件变量。在锁基础上实现协程等待+通知机制,适合生产者消费者模型。
python
cond = asyncio.Condition()
async def consumer():
async with cond:
await cond.wait()
- cond.wait(),Condition对象等待方法。释放锁,阻塞等待通知,收到通知后重新获取锁继续执行。
python
async with cond:
await cond.wait()
- cond.notify(n=1),Condition对象通知方法。唤醒最多n个正在等待该条件的协程。
python
async with cond:
cond.notify(2)
- cond.notify_all(),Condition对象通知方法。唤醒全部等待该条件的协程。
python
async with cond:
cond.notify_all()
- asyncio.Queue,异步队列,协程安全队列。协程之间安全传递数据,支持put、get,满队列put阻塞、空队列get阻塞,生产者消费者常用。
python
q = asyncio.Queue(maxsize=10)
await q.put(data)
item = await q.get()
- q.put(),异步队列放入元素方法。往队列写入数据,队列满时当前协程阻塞直到有空位。
python
await q.put(100)
- q.get(),异步队列取出元素方法。从队列读取数据,队列为空时当前协程阻塞直到有数据。
python
val = await q.get()
- q.qsize(),异步队列查询大小方法。返回当前队列里面元素数量。
python
print(q.qsize())
- q.empty(),异步队列判空方法。队列无元素返回True,否则False。
python
if q.empty():
pass
- q.full(),异步队列判满方法。队列达到maxsize上限返回True。
python
if q.full():
pass
- asyncio.Task.cancel(),Task任务取消方法。向任务发送取消请求,任务在await点抛出 CancelledError,终止协程。
python
task = asyncio.create_task(work())
task.cancel()
try:
await task
except asyncio.CancelledError:
print("任务取消")
- task.done(),Task对象状态查询方法。判断任务是否完成(正常结束、异常、被取消都算done),返回bool。
python
if task.done():
pass
- asyncio.Future,底层Future对象,Task继承自Future。底层可等待对象,用于占位异步结果,手动set_result/set_exception,多用于封装底层回调。
python
fut = asyncio.Future()
fut.set_result("ok")
res = await fut
栗子:
python
import asyncio
async def fetch(url, sem):
async with sem:
print(f"开始请求 {url}")
try:
await asyncio.sleep(0.8)
return f"{url} 响应成功"
except asyncio.TimeoutError:
return f"{url} 请求超时"
async def main():
sem = asyncio.Semaphore(5)
url_list = [f"url-{i}" for i in range(12)]
task_list = []
for url in url_list:
coro = asyncio.wait_for(fetch(url, sem), timeout=3)
task_list.append(coro)
results = await asyncio.gather(*task_list)
for ret in results:
print(ret)
if __name__ == "__main__":
asyncio.run(main())
python
import asyncio
async def producer(q: asyncio.Queue, event: asyncio.Event):
for i in range(8):
print(f"生产者生产数据: {i}")
await q.put(i)
await asyncio.sleep(0.3)
event.set()
print("生产者:全部生产完成")
async def consumer(q: asyncio.Queue, event: asyncio.Event, lock: asyncio.Lock):
while True:
if q.empty() and event.is_set():
break
try:
data = await asyncio.wait_for(q.get(), timeout=0.5)
async with lock:
print(f"消费者处理数据: {data}")
await asyncio.sleep(0.6)
q.task_done()
except asyncio.TimeoutError:
continue
async def main():
q = asyncio.Queue(maxsize=4)
event = asyncio.Event()
lock = asyncio.Lock()
task_prod = asyncio.create_task(producer(q, event))
task_cons = asyncio.create_task(consumer(q, event, lock))
await asyncio.gather(task_prod, task_cons)
print("程序结束")
if __name__ == "__main__":
asyncio.run(main())