从概念上讲,我们都知道多进程和多线程,而协程实际上是在单线程中实现了多个并发。从句法上看,协程类似于生成器,是在定义体中包含yield关键字的函数。不同的是协程的yield通常出现在表达式的右边:datum=yield。这让初学者瞬间觉得yield关键字不好用。本来以为yield只是简单的暂停执行返回一个值,但是可以放在右边吗?从生成器到协程,我们先来看协程最简单的使用示例:>>>defsimple_coroutine():...print("->coroutinestarted")...x=yield...print("->coroutinereceived:",x)...>>>my_coro=simple_coroutine()>>>my_coro>>>next(my_coro)->coroutinestarted>>>my_coro.send(42)->coroutinereceived:42Traceback(mostrecentcalllast):File"",line1,inStopIteration之所以yield可以放在右边,是因为协程可以接收调用者using.send()推送的值。yield放在右边之后,可以在它的右边放置另一个表达式,请看下面的例子:defsimple_coro2(a):b=yieldac=yielda+bmy_coro2=simple_coro2(14)next(my_coro2)my_coro2.send(28)my_coro2.send(99)的执行过程是:调用next(my_coro2),执行yielda,输出14。调用my_coro2.send(28),将28赋值给b,然后执行yielda+b,输出42。调用my_coro2.send(99),将99赋值给c,协程结束。由此可以得出,对于b=yielda这行代码,=右边的代码是在赋值之前执行的。例子中需要调用next(my_coro)来启动生成器,让程序暂停在yield语句处,然后就可以发送数据了。这是因为协程有四种状态:'GEN_CREATED'等待开始执行'GEN_RUNNING'解释器正在执行'GEN_SUSPENDED'在yield表达式处暂停'GEN_CLOSED'执行完后,只能在GEN_SUSPENDED状态发送数据,这一步就完成了inadvance叫做预激,可以调用next(my_coro)预激或者my_coro.send(None)预激,效果是一样的。预激协程协程必须预激后才能使用,即在send之前先调用next,使协程处于GEN_SUSPENDED状态。但是这件事经常被遗忘。为了避免忘记,可以定义一个预激装饰器,如:fromfunctoolsimportwrapsdefcoroutine(func):@wraps(func)defprimer(*args,**kwargs):gen=func(*args,**kwargs)next(gen)returngenreturnprimer但其实Python提供了一种更优雅的方式,叫做yieldfrom,它会自动预激发协程。自定义预烧装饰器与yieldfrom不兼容。yieldfromyieldfrom等同于其他语言中的await关键字。它的作用是:在生成器gen中使用yieldfromsubgen()时,subgen会获得控制权,并将输出值传递给gen的调用者,即调用者可以直接控制subgen。同时,gen阻塞,等待subgen终止。yieldfrom可用于简化for循环中的yield:forcin"AB":yieldcyieldfrom"AB"yieldfromx表达式对x所做的第一件事是调用iter(x)以从中获取迭代器。但是yieldfrom的作用远不止于此。它更重要的功能是打开双向通道。如下图所示:这个图信息量很大,也比较难理解。首先我们要了解这三个概念:caller、delegategenerator、sub-generator。说白了,调用者就是main函数,也就是大家熟知的程序入口main函数。#客户端代码,也就是调用者defmain(data):#<8>results={}forkey,valuesindata.items():group=grouper(results,key)#<9>next(group)#<10>forvalueinvalues:group.send(value)#<11>group.send(None)#重要!<12>#print(results)#uncommenttodebugreport(results)委托生成器是包含一个带有yieldfrom语句的函数,即协程。#委托生成器defgrouper(results,key):#<5>whileTrue:#<6>results[key]=yieldfromaverager()#<7>子生成器是右侧的子yieldfrom语句协程。#子生成器defaverager():#<1>total=0.0count=0average=NonewhileTrue:term=yield#<2>iftermisNone:#<3>breaktotal+=termcount+=1average=total/countreturnResult(count,average)#<4>这比术语舒服多了。然后是5行:send、yield、throw、StopIteration、close。当send协程挂在yieldfrom表达式时,主函数可以通过yieldfrom表达式向yieldfrom语句右侧的子协程发送数据。yieldyieldfrom语句右侧的子协程通过yieldfrom表达式将输出值发送给主函数。throwmain函数通过group.send(None)传入一个None值,从而终止yieldfrom语句右侧的子协程的while循环,从而将控制权交还给协程,并且可以继续执行,否则会一直暂停在yieldfrom语句中。在StopIterationyieldfrom语句之后的生成器函数返回后,解释器抛出StopIteration异常。并将返回值附在异常对象上,此时协程会恢复。closemain函数执行后,会调用close()方法退出协程。大体流程清楚,更多技术细节不再继续研究。有时间的话可以在以后的Python原理系列中学习。yieldfrom通常与Python3.4标准库中的@asyncio.coroutine装饰器结合使用。协程作为累加器这是协程的常见用法,代码如下:defaverager():total=0.0count=0average=NonewhileTrue:#<1>term=yieldaverage#<2>total+=termcount+=1average=total/count协程实现并发这里的例子有点复杂核心代码片段是:#BEGINTAXI_PROCESSdeftaxi_process(ident,trips,start_time=0):#<1>"""Yield模拟器在每次状态变化时发出事件"""time=yieldEvent(start_time,ident,'leavegarage')#<2>foriinrange(trips):#<3>time=yieldEvent(time,ident,'pickuppassenger')#<4>time=yieldEvent(time,ident,'dropoffpassenger')#<5>yieldEvent(time,ident,'goinghome')#<6>#taxi过程结束#<7>#ENDTAXI_PROCESSdefmain(end_time=DEFAULT_END_TIME,num_taxis=DEFAULT_NUMBER_OF_TAXIS,seed=None):"""初始化随机生成器,构建过程并运行模拟"""ifseedisnotNone:random.seed(seed)#获得可重现的结果taxis={i:taxi_process(i,(i+1)*2,i*DEPARTURE_INTERVAL)foriinrange(num_taxis)}sim=Simulator(taxis)sim.run(end_time)这个例子展示了如何处理主循环中的事件以及如何通过发送数据来驱动协程是asyncio包的基本思想。使用协程而不是线程和回调来实现并发。