2.12 生成器与协程,你分清了吗? =============================== |image0| 之前写的 ``并发编程`` 系列文章,是我迄今务止,得到的反馈最好的一个系列,也是因为它我才能认识这么多优秀的朋友。 但是可能由于知识诅咒的原因,我在协程那里并没有详细讲解生成器和协程的关系,不少人也因此还是懵懵懂懂的状态。 刚好前两天,有位读者朋友问我这个问题,我觉得有必要写一篇文章来讲解一下。 如果你是小白,刚接触这块知识的话,那么为你防止你更好的理解今天的内容,你最好预先下之前的写的入门级文章:\ `从生成器使用入门协程(七) `__ 最好的理解方式,莫过于用例子做个对比。 如你所见,下面这代码将定义一个生成器的。 .. code:: python import time def eat(): while True: if food: print("小明 吃完{}了".format(food)) yield print("小明 要开始吃{}...".format(food)) time.sleep(1) food = None MING = eat() # 产生一个生成器 MING.send(None) # 预激 food = "面包" MING.send('面包') MING.send('苹果') MING.send('香肠') 运行一下,从结果中可以看出,不管我们塞给小明什么东西,小明都将只能将他们当成面包吃。 :: 小明 要开始吃面包... 小明 吃完面包了 小明 要开始吃面包... 小明 吃完面包了 小明 要开始吃面包... 小明 吃完面包了 那再来看一下协程的。 .. code:: python import time def eat(): food = None while True: if food: print("小明 吃完{}了".format(food)) food = yield print("小明 开始吃{}...".format(food)) time.sleep(1) MING = eat() # 产生一个生成器 MING.send(None) # 预激 MING.send('面包') MING.send('苹果') MING.send('香肠') 运行一下,从结果中可以看出,小明已经可以感知我们塞给他的是什么食物。 :: 小明 开始吃面包... 小明 吃完面包了 小明 开始吃苹果... 小明 吃完苹果了 小明 开始吃香肠... 小明 吃完香肠了 仔细观察一下,上面两段代码并没有太大的区别,我们将主要关注点集中在 ``yidld`` 关键词上。 可以发现,生成器里 ``yield`` 左边并没有变量,而在协程里,\ ``yield`` 左边有一个变量。 在函数被调用后,一个生成器就产生了,而一般的生成器不能再往生成器内部传递参数了,而这个当生成器里的 yield 左边有变量时,就不一样了,它仍然可以在外部接收新的参数。这就是生成器与协程的最大区别。 **协程的优点:** - 线程属于系统级别调度,而协程是程序员级别的调度。使用协程避免了无意义的调度,减少了线程上下文切换的开销,由此可以提高性能。 - 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。 - 无需原子操作锁定及同步的开销 - 方便切换控制流,简化编程模型 **协程的缺点:**   (1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。   (2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序 协程很类似于Javascript单线程下异步处理的概念,协程同样是单线程的,之所以能够进行并发是因为通过某种方式保存了执行栈的上下文,在一定条件下将执行权交由其他栈,在一定条件下又通过执行栈上下文恢复栈。 -------------- |image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png .. |image1| image:: http://image.iswbm.com/20200607174235.png