前往顾页
以后地位: 主页 > 收集编程 > Ajax实例教程 >

golang的并发不即是并行

时候:2017-10-07 22:51来源:知行网www.zhixing123.cn 编辑:麦田守望者

先 看下面一道口试题:
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()
}


在不履行代码的前提下,脑补一下输入成果应当是甚么。

我再看到这道题时,起首想到输入应当是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


意不料外? 惊不欣喜?

为甚么会是如许的成果, 再翻阅了谷歌官方出品的golang文档以后,总算搞到了一些头绪。

并发不即是并行

golang的核心开辟职员Rob Pike特地提到了这个话题(有兴趣可以看这个视频或看原文PPT)

固然我们在for循环中利用了go 建立了一个goroutine,我们想当然会以为,每次循环变量时,golang必然会履行这个goroutine,然后输入当时的变量。 这时候,我们就堕入了思惟定势。 默许并发即是并行。

固然,经由过程go建立的goroutine是会并发的履行此中的函数代码。 但必然会遵循我们所假想的那样每次循环时履行吗? 答案是不是定的!

Rob Pike特地提到了golang中并发指的是代码布局中的某些函数逻辑上可以同时运行,但物理上一定会同时运行。而并行则指的就是在物理层面也就是利用了不合CPU在履行不合或不异的任务。

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官方已发过预警提示。 尽管本身看官方文档的习惯,所以直接栽坑里了。

幸亏及时发明了本身的不足,亡羊补牢,为时未晚吧。

顶一下
(1)
100%
踩一下
(0)
0%
------分开线----------------------------
标签(Tag):Golang
------分开线----------------------------
颁发评论
请自发遵循互联网相关的政策法规,严禁公布色情、暴力、革命的谈吐。
评价:
神色:
考证码:点击我更换图片
猜你感兴趣