Python协程和异步IO
同步和异步的概念
同步:是指代码调用IO操作时,必须等待IO操作完成后才返回的调用方式。
异步:是指代码调用IO操作时,不必等待IO操作完成就可以返回的调用方式。
协程
协程(coroutine)又称微线程,是运行在单线程中的“并发”,可以理解为轻量级的线程,对于频繁进行IO操作的任务,一般选择使用协程。
协程相对于线程而言切换的开销更小,是由程序自身控制的。协程的本质是单线程执行,要想利用多核CPU,可以在程序中开启多进程,每个进程内再开启多个线程,每个线程内再开启协程。这样既可以充分利用CPU,又能发挥协程的高效率,可获得极高的性能。
Python中协程的发展:
- Python2中使用
yield+send()
语法。 - Python3.3中引入
yield from
可以接收返回值。 - Python3.4中引入了
asyncio
模块,直接内置了对异步IO的支持。 - Python3.5增加了
async
和await
关键字。 - Python3.7使用
async def + await
的方式定义协程。
asyncio
asyncio
模块提供了是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
官方文档:https://docs.python.org/zh-cn/3/library/asyncio-task.html
核心概念
asyncio
核心组件包括事件循环(Event Loop)、协程(Coroutine)、任务(Task)和未来对象(Future)等。
- 事件循环(Event Loop):在整个程序运行过程中不断循环执行,相当于无限循环,可以把一些函数注册到事件循环上,当满足条件时调用相应的协程来处理这些事件。
- 协程(Coroutine):协程对象,指的是使用
async
关键字定义的方法,它的调用不会被立即执行,而是会返回一个协程对象。协程对象需要注册到事件循环中,由事件循环调用。 - 任务(Task):任务是对协程对象的进一步封装,包含了任务的各个状态,被用来在事件循环中运行协程。
- 对象(Future):代表将来执行或没有执行的任务的结果,和Task没有本质上的区别。
基本示例
使用 asyncio
一般通过如下几步:
- 创建协程对象
- 定义事件循环
- 将协程转为task任务
- 将task任务加入到事件循环
import asyncio
import time
# 创建协程对象
async def get_info(name):
print("start....")
print(name)
await asyncio.sleep(2)
print("end...")
if __name__ == '__main__':
start_time = time.time()
# 定义事件循环
loop = asyncio.get_event_loop()
# 将协程转为task任务
task = loop.create_task(get_info("hello, world"))
# 将task任务加入到事件循环中触发
loop.run_until_complete(task)
# python3.7及之后
# asyncio.run(task)
print(time.time()-start_time)
注:以下两步,在Python3.7及之后可以使用 asyncio.run()
的方式
loop = asyncio.get_event_loop()
loop.run_until_complete()
asyncio.create_task()
是 Python3.7 加入的更高级的API,在 Python3.6时,使用 asyncio.ensure_future()
来创建 Future,Future 也是一个管理协程运行状态的对象,与 Task 没有本质上的区别。
获取协程的返回值
方式1:通过 task.result()
import asyncio
import time
async def get_info(name):
print("start...")
await asyncio.sleep(2)
return name
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
# get_future = asyncio.ensure_future(get_info("hello world"))
# loop.run_until_complete(get_future)
# print(get_future.result())
task = loop.create_task(get_info("hello world"))
loop.run_until_complete(task)
print(task.result())
print(time.time() - start_time)
方式2:通过 add_done_callback()
回调
import asyncio
async def get_info(name):
print("start...")
await asyncio.sleep(2)
return name
def callback(future):
print(future.result())
if __name__ == '__main__':
loop = asyncio.get_event_loop()
info = get_info("hello world")
task = loop.create_task(info)
task.add_done_callback(callback)
loop.run_until_complete(task)
注:要想给callback函数添加参数:
from functools import partial
# 参数必须放在future前面
def callback(language, future):
print(language)
print(future.result())
...
task.add_done_callback(partial(callback, "python"))
协程的并发
通常需要将多个协程注册到事件循环中,并发的运行。主要有两种方式实现:
asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks)) # 这里的*不能省略
asyncio.wait(tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
gather
和wait
的区别:
gather
是更高层级的API,使用更加灵活,可以将任务分组,它会把值直接返回。
tasks1 = [work(num) for num in range(0, 3)]
tasks2 = [work(num) for num in range(3, 6)]
group1 = asyncio.gather(*tasks1)
group2 = asyncio.gather(*tasks2)
results1, results2 = await asyncio.gather(group1, group2)
print(results1, results2)
wait
会返回done
和pending
。- done:表示已完成的任务
- pending:表示未完成的任务
done, pending = await asyncio.wait(tasks)
for task in done:
print('Task Result: ', task.result())
示例:
import asyncio
async def get_info(name):
print("start...")
print(name)
await asyncio.sleep(2)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
group1 = [get_info("Python") for i in range(3)]
group2 = [get_info("Golang") for i in range(3)]
loop.run_until_complete(asyncio.gather(*group1, *group2))
协程的嵌套
在一个协程中可以使用 await
调用另外一个协程。
import asyncio
async def func1(temp):
print('start...', temp)
await asyncio.sleep(2)
print('end...', temp)
return 'Hello Python'
async def main():
coroutine1 = func1(1)
coroutine2 = func1(2)
coroutine3 = func1(3)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
# await 一个task列表
done, pending = await asyncio.wait(tasks)
for task in done:
print('Task Result: ', task.result())
if __name__ == '__main__':
asyncio.run(main())
常见的函数
call_soon:立即执行
import asyncio
def callback(sleep_times):
print("sleep {} success".format(sleep_times))
def stoploop(loop):
loop.stop()
if __name__ == "__main__":
# 创建一个事件循环
loop = asyncio.get_event_loop()
# 立即启动callback函数
loop.call_soon(callback, 2)
# 执行完毕后,立即启动执行stoploop函数
loop.call_soon(stoploop, loop)
loop.run_forever()
call_later:设置一定时间启动执行
import asyncio
def callback(sleep_times):
print("sleep {} success".format(sleep_times))
def stoploop(loop):
loop.stop()
if __name__ == "__main__":
# 创建一个事件循环
loop = asyncio.get_event_loop()
loop.call_later(2, callback, 2)
loop.call_later(1, callback, 1)
loop.call_later(3, callback, 3)
loop.call_later(4, stoploop, loop)
loop.run_forever()
call_at:类似于call_later
, call_later
内部其实调用了call_later
,但是call_at
指定的时间不是传统意义上的时间而是loop内部的时间。
import asyncio
def callback(sleep_times, loop):
print("success time {}".format(loop.time()))
def stoploop(loop):
loop.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# loop内部的时间
now = loop.time()
loop.call_at(now+2, callback, 2, loop)
loop.call_at(now+1, callback, 1, loop)
loop.call_at(now+3, callback, 3, loop)
loop.call_at(now+6, stoploop, loop)
loop.run_forever()
call_soon_threadsafe:和 call_soon
用法一致,不过是线程安全的。
异步迭代器
- 什么是异步迭代器:
实现了__aiter__()
和__anext__()
方法的对象,__anext__
返回一个awaitable
对象,async_for
会处理异步迭代器的__anext__()
方法所返回的可等待对象,直到触发StopAsyncIteration
异常。
- 什么是异步可迭代对象:
可在 async for
语句中被使用的对象,必须通过它的__aiter__()
方法返回一个 asynchronous iterator
。
import asyncio
class Reader(object):
""" 自定义异步迭达器(同时也是异步可迭达对象) """
def __init__(self):
self.count = 0
async def readline(self):
await asyncio.sleep(1)
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
async def func():
# async for 只能写在协程函数内
async for item in Reader():
print(item)
if __name__ == '__main__':
asyncio.run(func())
异步上下文管理器
此种对象通过定义 __aenter__()
和 __aexit__()
方法来对 async with
语句中的环境进行控制。
import asyncio
class AsyncContextManager:
def __init__(self):
print("init...")
async def do_something(self):
# 异步操作数据库
return 'xxx'
async def __aenter__(self):
print("aenter...")
# 异步连接数据库
self.conn = await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("aexit...")
# 异步关闭数据库连接
await asyncio.sleep(1)
async def func():
# async with 必须嵌套在协程函数里面
async with AsyncContextManager() as f:
result = await f.do_something()
print(result)
if __name__ == '__main__':
asyncio.run(func())