zoukankan      html  css  js  c++  java
  • 用greenlet实现Python中的并发

    from greenlet import greenlet
     
    def test1():
        print 12
        gr2.switch()
        print 34
     
    def test2():
        print 56
        gr1.switch()
        print 78
     
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()

    这里创建了两个greenlet协程对象,gr1和gr2,分别对应于函数test1()和test2()。使用greenlet对象的switch()方法,即可以切换协程。上例中,我们先调用”gr1.switch()”,函数test1()被执行,然后打印出”12″;接着由于”gr2.switch()”被调用,协程切换到函数test2(),打印出”56″;之后”gr1.switch()”又被调用,所以又切换到函数test1()。但注意,由于之前test1()已经执行到第5行,也就是”gr2.switch()”,所以切换回来后会继续往下执行,也就是打印”34″;现在函数test1()退出,同时程序退出。由于再没有”gr2.switch()”来切换至函数test2(),所以程序第11行”print 78″不会被执行。

    12
    56
    34

    父子关系

    创建协程对象的方法其实有两个参数”greenlet(run=None, parent=None)”。参数”run”就是其要调用的方法,比如上例中的函数test1()和test2();参数”parent”定义了该协程对象的父协程,也就是说,greenlet协程之间是可以有父子关系的。如果不设或设为空,则其父协程就是程序默认的”main”主协程。这个”main”协程不需要用户创建,它所对应的方法就是主程序,而所有用户创建的协程都是其子孙。大家可以把greenlet协程集看作一颗树,树的根节点就是”main”,上例中的”gr1″和”gr2″就是其两个字节点。

    在子协程执行完毕后,会自动返回父协程。比如上例中test1()函数退出,代码会返回到主程序。让我们写个更清晰的例子来实验下:

    from greenlet import greenlet
     
    def test1():
        print 12
        gr2.switch()
        print 34
     
    def test2():
        print 56
     
    gr1 = greenlet(test1)
    gr2 = greenlet(test2, gr1)
    gr1.switch()
    print 78

    这里创建greenlet对象”gr2″时,指定了其父协程是”gr1″。所以在函数test2()里,虽然没有”gr1.switch()”代码,但是在其退出后,程序一样回到了函数test1(),并且执行”print 34″。同样,在test1()退出后,代码回到了主程序,并执行”print 78″。所以,最后的输出就是:

    12
    56
    34
    78

    如果上例中,”gr2″的父协程不是”gr1″而是”main”的话,那test2()运行完毕就会回到主程序并直接打印”78″,这样”print 34″就不会执行。大家可以试一试。

    还有一个重要的点,就是协程退出后,就无法再被执行了。如果上例在函数test1()中,再加一句”gr2.switch()”,运行的结果是一样的。因为第二次调用”gr2.switch()”,什么也不会运行。

    def test1():
        print 12
        gr2.switch()
        print 34
        gr2.switch()

    大家可能会感觉到父子协程之间的关系,就像函数调用一样,一个嵌套一个。的确,其实greenlet协程的实现就是使用了栈,其运行的上下文保存在栈中,”main”主协程处于栈底的位置,而当前运行中的协程就在栈顶。这同函数是一样。此外,在任何时候,你都可以使用”greenlet.getcurrent()”,获取当前运行中的协程对象。比如在函数test2()中执行”greenlet.getcurrent()”,其返回就等于”gr2″。

    异常

    既然协程是存放在栈中,那一个协程要抛出异常,就会先抛到其父协程中,如果所有父协程都不捕获此异常,程序才会退出。我们试下,把上面的例子中函数test2()的代码改为:

    def test2():
        print 56
        raise NameError

    程序执行后,我们可以看到Traceback信息:

      File "parent.py", line 14, in 
        gr1.switch()
      File "parent.py", line 5, in test1
        gr2.switch()
      File "parent.py", line 10, in test2
        raise NameError

    如果将”gr2″的父协程设为空,Traceback信息就会变为:

     File "parent.py", line 14, in 
        gr1.switch()
      File "parent.py", line 10, in test2
        raise NameError

    因此,如果”gr2″的父协程是”gr1″的话,异常先回抛到函数test1()的代码”gr2.switch()”处。所以,我们再对函数test1()改动下:

    def test1():
        print 12
        try:
            gr2.switch()
        except NameError:
            print 90
        print 34

    运行后的结果,如果”gr2″的父协程是”gr1″,则异常被捕获,并打印90。否则,异常会被抛出。以上实验很好的证明了,子协程抛出的异常会根据栈里的顺序,依次抛到父协程里。

    有一个异常是特例,不会被抛到父协程中,那就是”greenlet.GreenletExit”,这个异常会让当前协程强制退出。比如,我们将函数test2()改为:

    def test2():
        print 56
        raise greenlet.GreenletExit
        print 78

    那代码行”print 78″永远不会被执行。但这个异常不会往上抛,所以其父协程还是可以正常运行

    另外,我们可以通过greenlet对象的”throw()”方法,手动往一个协程里抛个异常。比如,我们在test1()里调一个throw()方法:

    def test1():
        print 12
        gr2.throw(NameError)
        try:
            gr2.switch()
        except NameError:
            print 90
        print 34

    这样,异常就会被抛出,运行后的Trackback是这样的:

      File "exception.py", line 21, in 
        gr1.switch()
      File "exception.py", line 5, in test1
        gr2.throw(NameError)

    如果将”gr2.throw(NameError)”放在”try”语句中,那该异常就会被捕获,并打印”90″。另外,当”gr2″的父协程不是”gr1″而是”main”时,异常会直接抛到主程序中,此时函数test1()中的”try”语句就不起作用了。

    协程间传递消息

    在介绍生成器时,我们聊过可以使用生成器的send()方法来传递参数。greenlet也同样支持,只要在其switch()方法调用时,传入参数即可。我们再来基于本文第一个例子改造下:

    from greenlet import greenlet
     
    def test1():
        print 12
        y = gr2.switch(56)
        print y
     
    def test2(x):
        print x
        gr1.switch(34)
        print 78
     
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()

    在test1()中调用”gr2.switch()”,由于协程”gr2″之前未被启动,所以传入的参数”56″会被赋在test2()函数的参数”x”上;在test2()中调用”gr1.switch()”,由于协程”gr1″之前已执行到第5行”y = gr2.switch(56)”这里,所以传入的参数”34″会作为”gr2.switch(56)”的返回值,赋给变量”y”。这样,两个协程之间的互传消息就实现了。

    生产者消费者的例子,改为greenlet实现吧:

    from greenlet import greenlet
     
    def consumer():
        last = ''
        while True:
            receival = pro.switch(last)
            if receival is not None:
                print 'Consume %s' % receival
                last = receival
     
    def producer(n):
        con.switch()
        x = 0
        while x < n:
            x += 1
            print 'Produce %s' % x
            last = con.switch(x)
     
    pro = greenlet(producer)
    con = greenlet(consumer)
    pro.switch(5)
  • 相关阅读:
    vim基本配置
    docker-compose.yml的使用
    centos7安装docker-compose
    gitlab常用命令
    搭建gitlab服务
    jdk安装
    安装部署mongodb
    源码安装部署redis
    febric的使用
    IntelliJ IDEA 快捷键终极大全,速度收藏!
  • 原文地址:https://www.cnblogs.com/Erick-L/p/7067649.html
Copyright © 2011-2022 走看看