前几天flisky分享了Goroutine的方面的东西,之后我忙着做git原理的分享没来得及总结,赶紧总结下,夜长梦多难免遗忘。
Goroutine这个东西其实挺好理解的,有了对tornado的理解,这个东西其实类似,只不过tornado是基于框架的ioloop,而Goroutine是基于语言的"ioloop"——这里加引号表示其实我现在不太明白具体是什么,但是可以肯定的是它在运行时提供了类似的东西,不论是用epoll实现还是select或者其他什么实现。个人理解它是在运行时提供了类似于os的进程调度机制(感谢@monnand指正),然后它的每一个Goroutine都相当于一个进程,这样才有了Goroutine抢占式的特性(目前版本还没有实现抢占式)。(如果我理解错了,欢迎指正)
上面只是一些推理,没有实际去看Go的实现,仅作参考。下面来通过两段代码来对比Python的Coroutine(协程)和Go的Goroutine。
先来看由yield实现的Coroutine:
.. code:: python
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
输出结果为::
--- 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
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)
}
}
来看下输出的结果::
--- 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/%E5%86%8D%E6%8E%A2-goroutine.html (注意看评论)
关于协程的调度: http://www.douban.com/note/251142022/ (源码级分析)
Goroutine性能测试:http://mikespook.com/2012/01/goroutine%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/
- from the5fire.com微信公众号:Python程序员杂谈