golang的并发不等于并行

转自我的博客 chinazt.ccgit

先看下面一道面试题:github

func main() { runtime.GOMAXPROCS(1) wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { fmt.Println("go routine 1 i: ", i) wg.Done() }() } for i := 0; i < 10; i++ { go func(i int) { fmt.Println("go routine 2 i: ", i) wg.Done() }(i) } wg.Wait() }

在不执行代码的前提下,脑补一下输出结果应该是什么。golang

我再看到这道题时,首先想到输出应该是0 -- 9 依次输出。 但执行后才大跌眼镜,错的不是一点半点。首先看一下,在我本地执行的结果:面试

go routine 2 i: 9 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 2 i: 0 go routine 2 i: 1 go routine 2 i: 2 go routine 2 i: 3 go routine 2 i: 4 go routine 2 i: 5 go routine 2 i: 6 go routine 2 i: 7 go routine 2 i: 8

意不意外? 惊不惊喜?markdown

为何会是这样的结果, 再翻阅了google官方出品的golang文档以后,总算搞到了一些头绪。并发

并发不等于并行

golang的核心开发人员Rob Pike专门提到了这个话题(有兴趣能够看这个视频或者看原文PPT)ide

虽然咱们在for循环中使用了go 建立了一个goroutine,咱们想固然会认为,每次循环变量时,golang必定会执行这个goroutine,而后输出当时的变量。 这时,咱们就陷入了思惟定势。 默认并发等于并行。函数

诚然,经过go建立的goroutine是会并发的执行其中的函数代码。 但必定会按照咱们所设想的那样每次循环时执行吗? 答案是否认的!post

Rob Pike专门提到了golang中并发指的是代码结构中的某些函数逻辑上能够同时运行,但物理上未必会同时运行。而并行则指的就是在物理层面也就是使用了不一样CPU在执行不一样或者相同的任务。ui

golang的goroutine调度模型决定了,每一个goroutine是运行在虚拟CPU中的(也就是咱们经过runtime.GOMAXPROCS(1)所设定的虚拟CPU个数)。 虚拟CPU个数未必会和实际CPU个数相吻合。每一个goroutine都会被一个特定的P(虚拟CPU)选定维护,而M(物理计算资源)每次回挑选一个有效P,而后执行P中的goroutine。

每一个P会将本身所维护的goroutine放到一个G队列中,其中就包括了goroutine堆栈信息,是否可执行信息等等。默认状况下,P的数量与实际物理CPU的数量相等。所以当咱们经过循环来建立goroutine时,每一个goroutine会被分配到不一样的P队列中。而M的数量又不是惟一的,当M随机挑选P时,也就等同随机挑选了goroutine。

在本题中,咱们设定了P=1。因此全部的goroutine会被绑定到同一个P中。 若是咱们修改runtime.GOMAXPROCS的值,就会看到另外的顺序。 若是咱们输出goroutine id,就能够看到随机挑选的效果:

func main() { wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) } fmt.Println("go routine 1 i: ", i, id) wg.Done() }() } for i := 0; i < 10; i++ { go func(i int) { var buf [64]byte n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField) if err != nil { panic(fmt.Sprintf("cannot get goroutine id: %v", err)) } fmt.Println("go routine 2 i: ", i, id) wg.Done() }(i) } wg.Wait() }

输出以下:

go routine 2 i: 9 24 go routine 1 i: 10 11 go routine 1 i: 10 5 go routine 1 i: 10 6 go routine 2 i: 3 18 go routine 1 i: 10 9 go routine 1 i: 10 10 go routine 1 i: 10 8 go routine 2 i: 0 15 go routine 2 i: 4 19 go routine 2 i: 6 21 go routine 1 i: 10 7 go routine 1 i: 10 14 go routine 2 i: 7 22 go routine 2 i: 8 23 go routine 1 i: 10 13 go routine 2 i: 5 20 go routine 1 i: 10 12 go routine 2 i: 1 16 go routine 2 i: 2 17 ⋊> ~/S/g/g/s/t/C/goroutine ./goroutine go routine 1 i: 10 11 go routine 2 i: 9 24 go routine 1 i: 10 6 go routine 1 i: 10 14 go routine 1 i: 10 9 go routine 1 i: 10 10 go routine 1 i: 10 12 go routine 2 i: 0 15 go routine 1 i: 10 13 go routine 1 i: 10 5 go routine 2 i: 1 16 go routine 2 i: 5 20 go routine 1 i: 10 7 go routine 2 i: 7 22 go routine 2 i: 3 18 go routine 2 i: 2 17 go routine 2 i: 4 19 go routine 1 i: 10 8 go routine 2 i: 8 23 go routine 2 i: 6 21

咱们再回到这道题中,虽然在循环中经过go定义了一个goroutine。但咱们说到了,并发不等于并行。所以虽然定义了,但此刻不见得就会去执行。须要等待M选择P以后,才能去执行goroutine。 关于golang中goroutine是如何进行调度的(GPM模型),能够参考Scalable Go Scheduler Design Doc或者LearnConcurrency

这时应该就能够理解为何会先输出goroutine2而后再输出goroutine1了吧。

下面咱们来解释为何goroutine1中输出的都是10.

goroutine如何绑定变量

在golang的for循环中,golang每次都使用相同的变量实例(也就是题中所使用的i)。 而golang之间是共享环境变量的。

当调度到这个goroutine时,它就直接读取所保存的变量地址,此时就会出现一个问题:goroutine保存的只是变量地址,因此变量是有可能被修改的

再结合题中的for循环,每次使用的都是同一个变量地址,也就是说i每次都在变化,到循环结束之时,i就变成了10. 而goroutine中保存的也只有i的内存地址而已,因此当goroutine1执行时,坚决果断的就把i的内容读了出来,多少呢? 10!

但为何goroutine2不是10呢?

反过来看goroutine2,就容易理解了。由于在每次循环中都从新生成了一个新变量,而后每一个goroutine保存的是各自新变量的地址。 这些变量相互之间互不干扰,不会被任何人所篡改。所以在输出时,会从0 - 9依次输出。

其实这些问题,golang官方已经发过预警提示。 只管本身看官方文档的习惯,因此直接栽坑里了。

好在及时发现了本身的不足,亡羊补牢,为时未晚吧。

若是您认为此文章对您有所帮助,请您点击推荐。进步来源于思想的碰撞,因此欢迎你们踊跃留言评论。
相关文章
相关标签/搜索