python generator与coroutine

系统 1342 0

python  generator与coroutine

 

协程

简单介绍

协程,又称微线程,纤程,英文名Coroutine。
协程是一种用户态的轻量级线程,又称微线程。
协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

 

优缺点

优点:
1.无需线程上下文切换的开销
2.无需原子操作锁定及同步的开销
3.方便切换控制流,简化编程模型
4.高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。

缺点:
1.无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
但是多进程+协程,可以充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
2.进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。

 

协程可以用在哪些场景呢

            
              可以归纳为非阻塞等待的场景,如游戏编程,异步IO,事件驱动。
            
          

 

协程详解

Python对协程的支持是通过generator(生成器)实现的。

要理解生成器,我们先要理解迭代器。什么是迭代器?

            在python中一个可以迭代的数据调用iter方法,就可以得到一个迭代器,这个迭代器一定具有next方法,在调用这个迭代器的next方法时,迭代器就回返回它的下一个值,当迭代器中没有值可以返回了,就回抛出一个名为StopIteration的异常,停止迭代。
          

什么是生成器?

 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
 生成器是个比较特殊的可迭代对象,它与其他的可迭代对象不太一样的地方,其他的可迭代对象需要调用iter方法,返回个迭代器对象,然后通过迭代器对象去执行next方法,获取迭代器中的值,但是生成器直接可以被迭代,无需执行iter方法。

至少我们现在要明白

1.带有 yield 的函数不再是一个普通函数,而是一个生成器generator.
2.生成器是可以迭代的,使用next()方法.
3.生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。

4.协程是通过生成器的实现的
5.协程有四个状态,如下:
•'GEN_CREATED' 等待开始执行。
•'GEN_RUNNING' 解释器正在执行。
•'GEN_SUSPENDED' 在 yield 表达式处暂停。
•'GEN_CLOSED' 执行结束。

 

第一个示例程序

test为生成器,即可以迭代,我们可以使用next方法迭代。

第11行创建生成器test,在python的函数(function)定义中,只要出现了yield表达式(Yield expression),那么事实上定义的是一个 generator function , 调用这个generator function返回值是一个 generator。

第12行打印生成器。

第14行我们可以了生成器的状态,结果为:GEN_CREATED。

第15行第一次执行next方法,send和next操作都是调用生成器,而第一调用生成器就是启动生成器,启动生成器必须使用next()语句或是send(None)启动生成器,不能使用send发送一个非None的值,例如send(None),程序运行到yield 1,程序在这里暂停执行,并返回1,我们要明白在这里yield有了return的功能,返回了1,而且还暂停了程序。

第16行我们再次打印生成器的状态,结果:GEN_SUSPENDED。

第17行第二次执行next方法,程序运行到yield 2,程序在这里暂停,并返回2。

现在就很清晰了,使用yield可以切出生成器,它还有return的功能,切进生成器可以使用next()方法。

调用生成器的next(),将运行到yield位置,此时暂停执行环境,并返回这条语句yield关键词后面跟随的值。 这是next()的使用方法。

            
               1
            
            
              import
            
            
               inspect

            
            
               2
            
            
              def
            
            
               func1():

            
            
               3
            
            
              yield
            
             1

            
               4
            
            
              print
            
            (
            
              "
            
            
              第一个yield执行完成~
            
            
              "
            
            
              )

            
            
               5
            
            
              yield
            
             2

            
               6
            
            
              print
            
            (
            
              "
            
            
              第二个yield执行完成~
            
            
              "
            
            
              )

            
            
               7
            
            
              yield
            
             3

            
               8
            
            
              print
            
            (
            
              "
            
            
              第三个yield执行完成~
            
            
              "
            
            
              )

            
            
               9
            
            
              10
            
            
              11
            
             test =
            
               func1()

            
            
              12
            
            
              print
            
            
              (test)

            
            
              13
            
            
              14
            
            
              print
            
            (
            
              "
            
            
              还未执行next:
            
            
              "
            
            
              ,inspect.getgeneratorstate(test))

            
            
              15
            
            
              next(test)

            
            
              16
            
            
              print
            
            (
            
              "
            
            
              第一次执行next:
            
            
              "
            
            
              ,inspect.getgeneratorstate(test))

            
            
              17
            
            
              next(test)

            
            
              18
            
            
              print
            
            (
            
              "
            
            
              第二次执行next:
            
            
              "
            
            
              ,inspect.getgeneratorstate(test))

            
            
              19
            
            
              next(test)

            
            
              20
            
            
              print
            
            (
            
              "
            
            
              第三次执行next:
            
            
              "
            
            
              ,inspect.getgeneratorstate(test))

            
            
              21
            
            
              next(test)

            
            
              22
            
            
              print
            
            (
            
              "
            
            
              第四次执行next:
            
            
              "
            
            ,inspect.getgeneratorstate(test))
          

 

运行结果:

            
              
                
Traceback (most recent call last):
还未执行next: GEN_CREATED
第一次执行next: GEN_SUSPENDED
第一个yield执行完成
              
              ~
              
                
第二次执行next: GEN_SUSPENDED
第二个yield执行完成
              
              ~
              
                
  File 
              
              
                "
              
              
                C:/Pycham/异步编程/test3.py
              
              
                "
              
              , line 21, 
              
                in
              
              
                
                  
第三次执行next: GEN_SUSPENDED
第三个yield执行完成
                
                ~
                
                  
    next(test)
StopIteration
                
              
            
          

 

 

第二个示例程序

在上面的列子我们使用next()切进了生成器,但是每次切换进生成器,都没有传入参数,接下来将介绍send()方法,send()方法不仅可以切进生成器,而且还可以携带参数。

除了next和send方法,generator还提供了两个实用的方法,throw和close,这两个方法加强了caller对generator的控制。send方法可以传递一个值给generator,throw方法在generator挂起的地方抛出异常,close方法让generator正常结束(之后就不能再调用next send了)。

            
               1
            
            
              import
            
            
               sys

            
            
               2
            
            
              def
            
            
               func2(a):

            
            
               3
            
            
              print
            
            (
            
              '
            
            
              -> Started: a =
            
            
              '
            
            
              , a)

            
            
               4
            
                 b = 
            
              yield
            
            
               a

            
            
               5
            
            
              print
            
            (
            
              '
            
            
              -> Received: b =
            
            
              '
            
            
              , b)

            
            
               6
            
                 c = 
            
              yield
            
             a +
            
               b

            
            
               7
            
            
              print
            
            (
            
              '
            
            
              -> Received: c =
            
            
              '
            
            
              , c)

            
            
               8
            
            
               9
            
             test = func2(2
            
              )

            
            
              10
            
             value = next(test)
            
              #
            
            
               协程执行到`b = yield a`处暂停,等待为b赋值,并返回a
            
            
              11
            
            
              print
            
            
              (value)

            
            
              12
            
             value = test.send(88)
            
              #
            
            
               协程执行到`c = yield a + b`处暂停,等待为c赋值,并返回a + b
            
            
              13
            
            
              print
            
            
              (value)

            
            
              14
            
            
              try
            
            
              :

            
            
              15
            
                 test.send(11
            
              )

            
            
              16
            
            
              except
            
            
               StopIteration:

            
            
              17
            
                 sys.exit(0)
          

运行结果:

            -> Started: a = 2
2
-> Received: b = 88
90
-> Received: c = 11
          

 

 

第三个示例程序

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

            
               1
            
            
              import
            
            
               time

            
            
               2
            
            
               3
            
            
              def
            
            
               cumtom(name):

            
            
               4
            
            
              print
            
            (
            
              '
            
            
              %s准备吃包子
            
            
              '
            
             %
            
              name)

            
            
               5
            
                 time.sleep(1
            
              )

            
            
               6
            
            
              while
            
             1
            
              :

            
            
               7
            
                     count=
            
              yield
            
            
               8
            
            
              print
            
            (
            
              '
            
            
              %s吃到第%d个包子
            
            
              '
            
             %
            
              (name,count))

            
            
               9
            
            
              10
            
            
              def
            
            
               producter():

            
            
              11
            
            
                  next(con1)

            
            
              12
            
            
                  next(con2)

            
            
              13
            
            
              #
            
            
               con1.__next__()
            
            
              14
            
            
              #
            
            
               con2.__next__()
            
            
              15
            
                 n=1

            
              16
            
            
              while
            
             1
            
              :

            
            
              17
            
                     time.sleep(1
            
              )

            
            
              18
            
            
              print
            
            (
            
              '
            
            
              已经生产出来%d、%d个包子
            
            
              '
            
             %(n,n+1
            
              ))

            
            
              19
            
            
              #
            
            
              通过send方法通知
            
            
              20
            
            
                      con1.send(n)

            
            
              21
            
                     con2.send(n+1
            
              )

            
            
              22
            
                     n+=2

            
              23
            
            
              24
            
            
              #
            
            
              cumtom函数里面有yield,这里传递参数,会创建一个生成器对象,(提前做了预处理)
            
            
              25
            
             con1=cumtom(
            
              '
            
            
              cumtom1
            
            
              '
            
            
              )

            
            
              26
            
             con2=cumtom(
            
              '
            
            
              cumtom2
            
            
              '
            
            
              )

            
            
              27
            
             producter()
          

 


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论