从概念上来说,咱们都晓得多过程和多线程,而协程其实是在单线程中实现多并发。从句法上看,协程与生成器相似,都是定义体中蕴含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: 42Traceback (most recent call last): File "<input>", line 1, in <module>StopIteration
之所以yield能够放左边,是因为协程能够接管调用方应用.send()推送的值。
yield放在左边当前,它的左边还能再放个表达式,请看上面这个例子:
def simple_coro2(a): b = yield a c = yield a + bmy_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 wrapsdef 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_PROCESSdef 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包底层的根本思维。应用协程代替线程和回调,实现并发。