Golang当将GOMAXPROCS设置为1时会发生什么
最近面试的时候出了一道题,没有打上来,后面研究了一下,在这里分享出来
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("A", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("B", i)
wg.Done()
}(i)
}
wg.Wait()
以上这段代码会输出什么?
我的 golang 的版本是 1.17.4,不同的版本可能输出的不同,这里我只提我自己的版本,输出结果为:
B 9
A 10
A 10
A 10
A 10
A 10
A 10
A 10
A 10
A 10
A 10
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
这里我们看到 A 的话,全是 10,B 的话,9 是第一个执行,后面基本上全是顺序执行了,那造成这个情况的原因是什么呢?话不多说,直接看源码,当我们在用一个 go 的关键词时,编译器会给 go 解析为 newproc 函数,在 runtime/proc.go 中
大致意思就是创建一个新的 g 来运行 fn,size 为参数的大小,然后把他放入到等待运行的 g 队列中,newproc1 就是来创建一个新的 g 的
func newproc(siz int32, fn *funcval) {
argp := add(unsafe.Pointer(&fn), sys.PtrSize)
gp := getg()
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, argp, siz, gp, pc)
_p_ := getg().m.p.ptr()
runqput(_p_, newg, true)
if mainStarted {
wakep()
}
})
}
runqput 是将 g 放入到本地可运行队列中,如果第三个参数,next 为 true 的话,runqput 将会把 g 放入到 p.runnext 上,如果为 false 的话,将 g 添加到可运行队列的尾部,在 runnext 中的 g 永远会被最先调度执行
func runqput(_p_ *p, gp *g, next bool) {
if randomizeScheduler && next && fastrand()%2 == 0 {
next = false
}
if next {
retryNext:
oldnext := _p_.runnext
// 把g放入到_p_.runnext上
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
gp = oldnext.ptr()
}
//将原_p_.runnext上的g放入到队列_p_.runq中
retry:
h := atomic.LoadAcq(&_p_.runqhead) // load-acquire, synchronize with consumers
t := _p_.runqtail
if t-h < uint32(len(_p_.runq)) {
_p_.runq[t%uint32(len(_p_.runq))].set(gp)
atomic.StoreRel(&_p_.runqtail, t+1) // store-release, makes the item available for consumption
return
}
if runqputslow(_p_, gp, h, t) {
return
}
goto retry
}
看到这里就是创建一个协程的过程,但是需要解答这个题目,还需要掌握一个知识,在 for i := 0; i < 10; i++ 遍历中, i 在内存中只会存在一份,即之后每次循环时遍历到的数据都是以值覆盖的方式赋给 i,i 的内存地址始终不变。
当看到这里,不知道大家理解了没有,当我们给线程数量设置为 1 时,循环的创建协程时,协程会优先放入到 p.runnext 上,后续新的协程创建时,会将新的协程再次放入到 p.runnext 上,然后再将之前的放入 p.runq 中,我们上面的程序执行下来,最后 B9 会放入到 p.runnext 上,所以会优先打印出来 B9, 然后在打印 A,A 读取 i 时,i 的值已经递增到了 10,所以 A 打印出来全部是 10,而 B 的话,是传递的参数,会保存起来,所以 B 的话,是从 0-8 顺序执行。
大致执行的逻辑是这样,有需要的小伙伴可以尝试阅读源码,如果文中有描述错误的地方,望指教,谢谢观看
推荐文章: