一篇文章带你读懂Python的协程
协程,又称微线程,纤程。英文名Coroutine。
协程的概念很早就提出来了,但直到近年来才广泛应用于某些语言(如Lua)。
子程序,或函数,在所有语言中都是层次调用,如A调用B,B在执行过程中调用C,C执行返回,B执行返回,最后A执行。
因此,子程序调用是通过栈实现的,一个线程就是执行子程序。
子程序调用总是一个入口,一次返回,调用顺序明确。协程调用不同于子程序。
协程看起来像子程序,但在执行过程中,可以在子程序内部中断,然后转而执行其他子程序,然后在适当的时候返回执行。
请注意,中断一个子程序,执行其他子程序,而不是函数调用,有点像CPU中断。例如,子程序A、B:
defA(): print'1' print'2' print'3' defB(): print'x' print'y' print'z'
假设由协程执行,在执行A的过程中,可以随时中断,执行B,B也可以在执行A的过程中中中断,结果可能是:
1 2 x y 3 z
但A中没有调用B,因此协程调用比函数调用更难理解。
看起来A、B的执行有点像多线程,但协程的特点是一个线程执行。与多线程相比,协程有什么优势?
优点是协程执行效率高。由于子程序切换不是线程切换,而是由程序本身控制,因此与多线程相比,线程数量越多,协程的性能优势就越明显。
第二个优点是不需要多线程锁定机制,因为只有一个线程,并且没有同时编写变量冲突。在协同过程中,只需判断状态即可控制共享资源,因此执行效率远高于多线程。
因为协程是一个线程执行,如何使用多核CPU?最简单的方法是多过程+协程,它不仅充分利用多核,而且充分发挥协程的高效率,可以获得极高的性能。
Python对协程的支持仍然非常有限,用于generator的yield可以在一定程度上实现协程。虽然支持不完全,但它可以发挥相当大的力量。
来看例子:
传统的生产者消费者模型是一个线程来编写信息,一个线程可以通过锁定机制来控制队列和等待,但如果你不小心,你可能会被锁定。
如果使用协议,生产者在生产消息后直接跳转到消费者开始执行。消费者执行后,切换回生产者继续生产,效率很高:
importtimedefconsumer(): r='' whileTrue: n=yieldrifnotn:return print('[CONSUMER]Consuming%s...'%n) time.sleep(1) r='200OK'defproduce(c): c.next() n=0 whilen<5: n=n+1 print('[PRODUCER]Producing%s...'%n) r=c.send(n) print('[PRODUCER]Consumerreturn:%s'%r) c.close()if__name__='__main__': c=consumer() produce(c)
执行结果:
注意到consumer函数是generator(生成器),将consumer传输到produce后:
首先调用c.next()启动生成器;
然后,一旦生产了东西,通过c,.send(n)切换到consumer执行;
consumer通过yield获取消息,然后通过yield将结果传回;
produce获得consumer处理结果,并继续生成下一条消息;
通过croduce决定不生产produce.close()关闭consumer,整个过程结束。
整个过程无锁,由一个线程执行,produce和consumer合作完成任务,因此被称为“协程”,而不是线程的抢占式多任务。
最后,Donaldd应用程序 Knuth的一句话总结了协程的特点:
“子程序是协程的特例。”