目录
文章目录
Python 的协程
Python 对协程的支持经历了多个版本:
- Python2.x 对协程的支持比较有限,通过 yield 关键字支持的生成器实现了一部分协程的功能但不完全。
- 第三方库 gevent 对协程有更好的支持。
- Python3.4 中提供了 asyncio 模块。
- Python3.5 中引入了 async/await 关键字。
- Python3.6 中 asyncio 模块更加完善和稳定。
- Python3.7 中内置了 async/await 关键字。
asyncio
- Asynchronous I/O(异步 I/O):只发出 I/O 的执行,并不等待 I/O 的结果,释放 CPU,提高程序运行效率。
- Event loop(事件循环):事件循环是一种处理多并发的有效手段。通过启动一个无限的事件循环,提供事件监测、事件触发等处理工作。可以将 Coroutines 对象注册到事件循环上,当特定的某个事件发生时,就会调用相应的协程函数。
- Co-routines(协程):通过 async def 声明一个协程函数,对协程函数的调用不会立即执行函数体,而是返回一个 Coroutines 对象。 Coroutines 对象需要注册到事件循环,由事件循环调用。
- Tasks(任务):是对 Coroutines 对象的进一步封装,包含了面向任务的各种状态,支持任务创建、任务取消等管理功能。在注册事件循环时,通过 run_until_complete() 方法会将 Coroutines 对象包装成为了一个 Task 对象。
- Futures(将来执行任务的结果):Future 类是 Task 类的父类,实现了 Task 对象执行结果的保存。
Event Loop
asyncio 编程模型的核心就是一个基于消息的事件循环,程序从 asyncio 模块中获取到一个 Event Loop 的引用,然后把需要执行的协程都扔到 Event Loop 中执行,就实现了异步 I/O。
asyncio 的 Event Loop 拥有多种方式去启动协程,最简单的一种就是使用 run_until_complete() 方法。
import asyncio
async def coroutine():
print('in coroutine')
return 'result'
coro = coroutine()
event_loop = asyncio.get_event_loop()
try:
print('entering event loop')
result = event_loop.run_until_complete(coro)
print(f'it returned: {result}')
finally:
print('closing event loop')
event_loop.close()
OUTPUT:
entering event loop
in coroutine
it returned: result
closing event loop
async 与 await
async 关键字用于创建一个协程(返回一个 Coroutines 对象),该 Coroutines 对象需要注册到事件循环,由事件循环调用。针对不同的场景场景有 async def、async for、async with 等几种使方式。
await 关键字用于针对阻塞的 I/O 操作进行挂起,作用与生成器中的 yield(让出)关键字相同,协程函数将会让出控制权。也就是说,当执行协程函数体的过程中遇到了 await 语句,Event Loop 就会把该协程挂起,继而执行其他的协程,直到其他的协程也挂起或者执行完毕后,再执行下一个协程。
async def
async def 用于声明一个协程函数,对协程函数的调用不会立即执行函数体,而是返回一个 Coroutines 对象。一个简单的例子如前文所述。
值的注意的是,async def 支持 “链式协程(Chain coroutines)”,即:父协程可以创建子协程,以此往复,形成一条由若干个协程组成的调用链,并且彼此之间遵循顺序执行。
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()
OUTPUT:
Compute 1 + 2 ...
1 + 2 = 3
async for
async for 就相当于一个异步的生成器(Generator)。在 Python3.5 之前,实现异步生成器是一件很麻烦的事情,需要定义一个类,并且定义 __iter__
和 __next__
方法,之后才能使用。
现在只需要使用 async for 就能够简单的实现了,而且还支持列表解析等语法。
import asyncio
async def g2():
yield 1
yield 2
async def g1():
async for v in g2():
print(v)
return [v * 2 async for v in g2()]
loop = asyncio.get_event_loop()
try:
result = loop.run_until_complete(g1())
print(f'Result is {result}')
finally:
loop.close()
async with
with 是 Python 的 Context Management(上下文管理)语法糖,async with 即是异步的 with。
- 实现 with 类型
class Sample:
def __enter__(self):
print "in __enter__"
return "Foo"
def __exit__(self, exc_type, exc_val, exc_tb):
print "in __exit__"
def get_sample():
return Sample()
with get_sample() as sample:
print "Sample: ", sample
注意,async with 语法糖同样需要首先实现 async with 类型。目前原生支持 async with 类型的第三方库有比较典型的 aiohttp。
import aiohttp
async def fetch_page(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
loop = asyncio.get_event_loop()
result = loop.run_until_complete(fetch_page('http://httpbin.org/get?a=2'))
print(f"Args: {result.get('args')}")
loop.close()
Future 与 Task
正如前文所言,async def 返回的 Coroutines 对象是不能直接运行的,而是在将 Coroutines 对象注册到 Event Loop 之后,由 run_until_complete() 方法将 Coroutines 对象包装成为了一个 Task 对象。
而 Task 类又是 Future 类的子类,Future 类实现了保存 Task 对象运行后的状态,Future 对象可以用于在未来获取协程的执行结果。
当有需要时,我们可以显式地创建了 task 对象。并且,在 task 对象加入 Event Loop 之前的状态为 Pending,执行当完成后的状态为 Finished。
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print("waiting:", x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
print(task)
loop.run_until_complete(task)
print(task)
print("Time:", now()-start)
OUTPUT:
<Task pending coro=<do_some_work() running at test_asyncio.py:8>>
waiting: 2
<Task finished coro=<do_some_work() done, defined at test_asyncio.py:8> result=None>
Time: 0.0010194778442382812
另外,除了 loop.create_task() 方法之外,也可以使用 asyncio.ensure_future(coroutine) 方法来将一个 Coroutines 对象封装成一个 Task 对象,但两者有着不同的使用场景。后者更多的是用于确保可以得到相应的 Future Result。
- loop.create_task
AbstractEventLoop.create_task(coro)
Schedule the execution of a coroutine object: wrap it in a future. Return a Task object.
Third-party event loops can use their own subclass of Task for interoperability. In this case, the result type is a subclass of Task.
This method was added in Python 3.4.2. Use the async() function to support also older Python versions.
- asyncio.ensure_future
asyncio.ensure_future(coro_or_future, *, loop=None)
Schedule the execution of a coroutine object: wrap it in a future. Return a Task object.
If the argument is a Future, it is returned directly.