Goroutine VS Coroutine

2019-03-01 09:52:26 浏览数 (1)

前几天flisky分享了Goroutine的方面的东西,之后我忙着做git原理的分享没来得及总结,赶紧总结下,夜长梦多难免遗忘。

Goroutine这个东西其实挺好理解的,有了对tornado的理解,这个东西其实类似,只不过tornado是基于框架的ioloop,而Goroutine是基于语言的"ioloop"——这里加引号表示其实我现在不太明白具体是什么,但是可以肯定的是它在运行时提供了类似的东西,不论是用epoll实现还是select或者其他什么实现。个人理解它是在运行时提供了类似于os的进程调度机制(感谢@monnand指正),然后它的每一个Goroutine都相当于一个进程,这样才有了Goroutine抢占式的特性(目前版本还没有实现抢占式)。(如果我理解错了,欢迎指正)

上面只是一些推理,没有实际去看Go的实现,仅作参考。下面来通过两段代码来对比Python的Coroutine(协程)和Go的Goroutine。

先来看由yield实现的Coroutine:

.. code:: python

代码语言:javascript复制
import random

def subtask():
    i = -1 
    while True:
        a = yield i
        i  = 1
        print "--- come from parent ", a

c = subtask()
c.next() # 启动协程,之后下面就可以send通信了
for i in range(10):
    sub = c.send(i)
    print "come from subtask ", sub

输出结果为::

代码语言:javascript复制
--- come from parent  0
come from subtask  0
--- come from parent  1
come from subtask  1
--- come from parent  2
come from subtask  2
--- come from parent  3
come from subtask  3
--- come from parent  4
come from subtask  4
--- come from parent  5
come from subtask  5
--- come from parent  6
come from subtask  6
--- come from parent  7
come from subtask  7
--- come from parent  8
come from subtask  8
--- come from parent  9
come from subtask  9

其中需要解释的部分我已经写了注释。这个结果很有意思,很好的反映了主协程(或者主函数)和子协程通信的过程已经执行的过程。它依然是按照正常的程序执行顺序执行,唯一不同的是,它是由两个部分相互协作执行的。带着这个结果来看下Go的实现。

Go的代码实现同样的逻辑:

.. code:: go

代码语言:javascript复制
package main

import (
    "fmt"
)

func subtask(in chan int, out chan int) {
    i := 0
    for {
        out <- i
        fmt.Println("--- come from parent ", <-in)
        i  
    }
}

func main() {
    var in = make(chan int, 1)
    var out = make(chan int)

    for i := 0; i < 10; i   {
        in <- i
        go subtask(in, out)
        tmp := <-out
        fmt.Println("come from subtask ", tmp)
    }
}

来看下输出的结果::

代码语言:javascript复制
--- come from parent  0
come from subtask  0
come from subtask  1
--- come from parent  1
come from subtask  0
--- come from parent  2
come from subtask  2
--- come from parent  3
come from subtask  0
--- come from parent  4
come from subtask  1
--- come from parent  5
come from subtask  0
--- come from parent  6
come from subtask  3
--- come from parent  7
come from subtask  0
--- come from parent  8
come from subtask  1

发现哪里不同了吗?从subtask中返回的值根本就不是按照顺序来的,也就是说主进程开启了10个Goroutine,这10个Goroutine在一定程度上是并发执行的。再反过来看Python版本的实现,虽然也是两部分但两者之间是相互协调执行的,也就是parent执行到一半,然后把控制权交给subtask,然后subtask执行到一半通过yield把控制权交给parent,如此相互执行。

再来看Goroutine执行的那个结果,如果要想达到Python的那样的效果怎么做,只能是通过加锁来实现。写到这你肯定会情不自禁的像我一样:噫,这不就是多线程吗。确实类似只不过不是直接运行在cpu上的,而是运行在Go的运行时上,至于再底层使用Thread还是什么其他的实现就不需要考虑了。

同时也侧面看出了python协程的优点,那就是在这种上下文切换并且需要保持逻辑顺序的情况下,不需要通过锁来实现。

看到这里不知道各位看官是否明白,我自己还是觉得对Goroutine这块还是有点模糊,还需要之后深入研究才行。

声明下,这篇文章有很多地方不一定正确,谨慎引用。

参考资料

flisky的gist: https://gist.github.com/flisky/7313394 (Python大牛,建议follow此人)

协程的好处:http://www.zhihu.com/question/20511233

再探协程:http://xiezhenye.com/2012/08/再探-goroutine.html (注意看评论)

关于协程的调度: http://www.douban.com/note/251142022/ (源码级分析)

Goroutine性能测试:http://mikespook.com/2012/01/goroutine性能测试/

0 人点赞