共计 3778 个字符,预计需要花费 10 分钟才能阅读完成。
从概念上来说,咱们都晓得多过程和多线程,而协程其实是在单线程中实现多并发。从句法上看,协程与生成器相似,都是定义体中蕴含 yield 关键字的函数。区别在于协程的 yield 通常呈现在表达式的左边:datum = yield。这一下就让初学者霎时感觉 yield 关键字不香了,原本认为 yield 就是简简单单的暂停执行棘手返回个值,后果还能放左边?
从生成器到协程
先看一个可能是协程最简略的应用示例:
>>> def simple_coroutine():
... print("-> coroutine started")
... x = yield
... print("-> coroutine received:", x)
...
>>> my_coro = simple_coroutine()
>>> my_coro
<generator object simple_coroutine at 0x0000019A681F27B0>
>>> next(my_coro)
-> coroutine started
>>> my_coro.send(42)
-> coroutine received: 42
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
之所以 yield 能够放左边,是因为协程能够接管调用方应用.send()推送的值。
yield 放在左边当前,它的左边还能再放个表达式,请看上面这个例子:
def simple_coro2(a):
b = yield a
c = yield a + b
my_coro2 = simple_coro2(14)
next(my_coro2)
my_coro2.send(28)
my_coro2.send(99)
执行过程是:
调用 next(my_coro2),执行 yield a,产出 14。
调用 my_coro2.send(28),把 28 赋值给 b,而后执行 yield a + b,产出 42。
调用 my_coro2.send(99),把 99 赋值给 c,协程终止。
由此得出结论,对于 b = yield a 这行代码来说,= 左边的代码在赋值之前执行。
在示例中,须要先调用next(my_coro)启动生成器,让程序在 yield 语句处暂停,而后才能够发送数据。这是因为协程有四种状态:
‘GEN_CREATED’ 期待开始执行
‘GEN_RUNNING’ 解释器正在执行
‘GEN_SUSPENDED’ 在 yield 表达式处暂停
‘GEN_CLOSED’ 执行完结
只有在 GEN_SUSPENDED 状态能力发送数据,提前做的这一步叫做预激,既能够调用next(my_coro)预激,也能够调用my_coro.send(None)预激,成果一样。
预激协程
协程必须预激能力应用,也就是 send 前,先调用 next,让协程处于 GEN_SUSPENDED 状态。然而这件事常常会遗记。为了防止遗记,能够定义一个预激装璜器,比方:
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return primer
但实际上 Python 给出了一个更优雅的形式,叫做 yield from,它会主动预激协程。
自定义预激装璜器和 yield from 是不兼容的。
yield from
yield from 相当于其余语言中的 await 关键字,作用是:在生成器 gen 中应用 yield from subgen()时,subgen 会取得控制权,把产出的值传给 gen 的调用方,即调用方能够间接管制 subgen。与此同时,gen 会阻塞,期待 subgen 终止。
yield from 能够用来简化 for 循环中的 yield:
for c in "AB":
yield c
yield from "AB"
yield from x 表达式对 x 做的第一件事就是,调用 iter(x),从中获取迭代器。
但 yield from 的作用远不止于此,它更重要的作用是关上双向通道。如下图所示:
这个图信息量很大,很难了解。
首先要了解这 3 个概念:调用方、委派生成器、子生成器。
调用方
说白了就是 main 函数,也就是家喻户晓的程序入口 main 函数。
# the client code, a.k.a. the caller def main(data): # <8> results = {} for key, values in data.items(): group = grouper(results, key) # <9> next(group) # <10> for value in values: group.send(value) # <11> group.send(None) # important! <12> # print(results) # uncomment to debug report(results)
委派生成器
就是蕴含了 yield from 语句的函数,也就是协程。
# the delegating generator def grouper(results, key): # <5> while True: # <6> results[key] = yield from averager() # <7>
子生成器
就是 yield from 语句左边跟着的子协程。
# the subgenerator def averager(): # <1> total = 0.0 count = 0 average = None while True: term = yield # <2> if term is None: # <3> break total += term count += 1 average = total/count return Result(count, average) # <4>
这比术语看着难受多了。
而后是 5 条线:send、yield、throw、StopIteration、close。
send
协程在 yield from 表达式处暂停时,main 函数能够通过 yield from 表达式把数据发给 yield from 语句左边跟着的子协程。
yield
yield from 语句左边跟着的子协程再把产出的值通过 yield from 表达式发给 main 函数。
throw
main 函数通过group.send(None),传入一个 None 值,让 yield from 语句左边跟着的子协程的 while 循环终止,这样控制权才会交回协程,能力继续执行,否则会始终暂在 yield from 语句暂停。
StopIteration
yield from 语句左边跟着的生成器函数返回之后,解释器会抛出 StopIteration 异样。并把返回值附加到异样对象上,此时协程会复原。
close
main 函数执行完当前,会调用 close()办法退出协程。
大体流程搞清楚了,更多的技术细节就不持续钻研了,有工夫的话,在当前的 Python 原理系列中再学习吧。
yield from 常常与 Python3.4 规范库里的@asyncio.coroutine装璜器联合应用。
协程用作累加器
这是协程的常见用处,代码如下:
def averager():
total = 0.0
count = 0
average = None
while True: # <1>
term = yield average # <2>
total += term
count += 1
average = total/count
协程实现并发
这里例子有点简单
外围代码片段是:
# BEGIN TAXI_PROCESS
def taxi_process(ident, trips, start_time=0): # <1>
"""Yield to simulator issuing event at each state change"""
time = yield Event(start_time, ident, 'leave garage') # <2>
for i in range(trips): # <3>
time = yield Event(time, ident, 'pick up passenger') # <4>
time = yield Event(time, ident, 'drop off passenger') # <5>
yield Event(time, ident, 'going home') # <6>
# end of taxi process # <7>
# END TAXI_PROCESS
def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
seed=None):
"""Initialize random generator, build procs and run simulation"""
if seed is not None:
random.seed(seed) # get reproducible results
taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL)
for i in range(num_taxis)}
sim = Simulator(taxis)
sim.run(end_time)
这个示例阐明了如何在一个主循环中处理事件,以及如何通过发送数据驱动协程。这是 asyncio 包底层的根本思维。应用协程代替线程和回调,实现并发。