协程,又称微线程,纤程。英文名Coroutine。
线程是零碎级别的它们由操作系统调度,而协程则是程序级别的由程序依据须要本人调度。在一个线程中会有很多函数,咱们把这些函数称为子程序,在子程序执行过程中能够中断去执行别的子程序,而别的子程序也能够中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断而后跳转执行别的代码,接着在之前中断的中央持续开始执行,相似与yield操作。
协程领有本人的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保留到其余中央,在切回来的时候,复原先前保留的寄存器上下文和栈,也就是晓得属于本人的变量,这就是为什么生成器在yield之后,能够持续应用yield 的变量的起因。因而:协程能保留上一次调用时的状态(即所有部分状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次来到时所处逻辑流的地位。
协程的长处:
(1)无需线程上下文切换的开销,协程防止了无意义的调度,由此能够进步性能(但也因而,程序员必须本人承当调度的责任,同时,协程也失去了规范线程应用多CPU的能力)
(2)无需原子操作锁定及同步的开销
(3)不便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU反对上万的协程都不是问题。所以很适宜用于高并发解决。
协程的毛病:
(1)无奈利用多核资源:协程的实质是个单线程,它不能同时将 单个CPU 的多个核用上,协程须要和过程配合能力运行在多CPU上.当然咱们日常所编写的绝大部分利用都没有这个必要,除非是cpu密集型利用。
(2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
接下来是用协程实现 生产者,消费者的经典案例:
def consumer(): print("[consumer]:i am hunger , i need food") while True: food = yield if food is None: break print(f"[consumer]: so delicious {food} baozi") print('i see')def producter(): g = consumer() #生成一个消费者对象 i = 0 next(g) #协程必须预激活,走到 下面yield处,而后挂着,期待调用方给它发消息 while i < 5: print(f"[producter]: make food {i} baozi") g.send(i) #应用生成器提供的api send,send中的值就是下面food的值; i += 1 print("[producter]: oh no, i am tried") g.close() #敞开协程,养成良好习惯producter()
[consumer]:i am hunger , i need food[producter]: make food 0 baozi[consumer]: so delicious 0 baozi[producter]: make food 1 baozi[consumer]: so delicious 1 baozi[producter]: make food 2 baozi[consumer]: so delicious 2 baozi[producter]: make food 3 baozi[consumer]: so delicious 3 baozi[producter]: make food 4 baozi[consumer]: so delicious 4 baozi[producter]: oh no, i am tried
值得注意点是:就算不必调用下面的g.close(),程序尽管会失常退出,然而这个协程并没有敞开,而且它的此时的状态是:GEN_SUSPENDED ;
>>> from demo_product import consumer>>> from inspect import getgeneratorstate>>> g = consumer()>>> getgeneratorstate(g)'GEN_CREATED'>>> next(g)[consumer]:i am hunger , i need food>>> getgeneratorstate(g)'GEN_SUSPENDED'>>> g.send(2)[consumer]: so delicious 2 baozi>>> g.send(5)[consumer]: so delicious 5 baozi>>> g.send(None)i seeTraceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration>>> getgeneratorstate(g)'GEN_CLOSED'>>> g = consumer()>>> next(g)[consumer]:i am hunger , i need food>>> g.close()>>> getgeneratorstate(g)'GEN_CLOSED'
从下面的代码中,咱们能够发现,协程对象是必须要预激活的,当协程激活后,协程在yield处停留。此时能够应用三种形式完结协程,一种是通过g.send(None)发送一个None值,而后失去异样StopIteration,另一种形式是通过g.close(),这种形式相比来说比拟优雅。还有一种形式就是通过g.throw(error),被动抛出一个异样,类型能够自定;假使 是想要协程返回值,那么就须要应用g.send(None)以及g.throw(error)这两种形式了。
下方是通过g.send(None)的形式,完结协程以及获取协程的返回值;
def consumer(): print("[consumer]:i am hunger , i need food") while True: food = yield if food is None: break print(f"[consumer]: so delicious {food} baozi") return "i see"def producter(): g = consumer() i = 0 next(g) while i < 5: print(f"[producter]: make food {i} baozi") g.send(i) i += 1 print("[producter]: oh no, i am tried") try: g.send(None) except StopIteration as e: res = e.value print(res)if __name__ == "__main__": producter()
其实python还提供一种更简略的形式获取协程的返回值,那就是应用关键字:yield from ,通过这种形式能够优雅的解决StopIteration的谬误,并且获取谬误中的返回值,不过这种形式得在委派生成器中应用,通过委派生成器连贯调用方与子生成器,这个委派生成器则是两则沟通的桥梁,具体细节我不在此多说,有趣味的能够深刻理解;上面是yield from根底演示,它省去了一层for循环。
>>> def gen():... for i in range(3):... yield i...>>> g = gen()>>> for i in g:... print(i)...012>>> def gen():... yield from range(3)...>>> g = gen()>>> for i in g:... print(i)...012>>>
总结:可见协程在实现异步上是如许不便,cpu只须要收回指令集,期待返回后果即可,在这期待的工夫内,cpu能够去干其余活,极大的进步了效率。正如《优化python代码的59个倡议》中提到的,第40条,应该通过应用协程进行并发操作。