首先,看一段有趣的代码:
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1) // 设置 p 的数量为 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()
}
结果:
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
这里考察的是单线程情况下调度的过程。你可以多次尝试,每次的结果都是如此,毫无例外,为什么呢?
首先,我们看看出队列的过程:
// Get g from local runnable queue.
// If inheritTime is true, gp should inherit the remaining time in the
// current time slice. Otherwise, it should start a new time slice.
// Executed only by the owner P.
func runqget(_p_ *p) (gp *g, inheritTime bool) {
// If there's a runnext, it's the next G to run.
next := _p_.runnext
// If the runnext is non-0 and the CAS fails, it could only have been stolen by another P,
// because other Ps can race to set runnext to 0, but only the current P can set it to non-0.
// Hence, there's no need to retry this CAS if it falls.
if next != 0 && _p_.runnext.cas(next, 0) {
return next.ptr(), true
}
for {
h := atomic.LoadAcq(&_p_.runqhead) // load-acquire, synchronize with other consumers
t := _p_.runqtail
if t == h {
return nil, false
}
gp := _p_.runq[h%uint32(len(_p_.runq))].ptr()
if atomic.CasRel(&_p_.runqhead, h, h+1) { // cas-release, commits consume
return gp, false
}
}
}
func (gp *guintptr) cas(old, new guintptr) bool {
return atomic.Casuintptr((*uintptr)(unsafe.Pointer(gp)), uintptr(old), uintptr(new))
}
func Casuintptr(ptr *uintptr, old, new uintptr) bool {
if *ptr == old {
*ptr = new
return true
}
return false
}
由于是单线程,不存在抢占的问题,出队列的过程可以认为是一个遍历 slice 的过程,也就是说,完全取决于入队列的顺序。这里需要注意的是,在出队列时,会先执行 _p_.runnext.cas(next, 0)
这个函数,这就解释了为什么会先输出 9.
现在,我们看看入队列的过程:
// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the _p_.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
// Executed only by the owner P.
func runqput(_p_ *p, gp *g, next bool) {
if randomizeScheduler && next && fastrandn(2) == 0 {
next = false
}
if next {
retryNext:
oldnext := _p_.runnext
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
// Kick the old runnext out to the regular run queue.
gp = oldnext.ptr()
}
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
}
// the queue is not full, now the put above must succeed
goto retry
}
上面的注释部分已经解释了整个过程,如果 next 为假,入队列将 g 添加到可运行队列的尾部,如果 next 为真,入队列将 g 放入 p 的槽中,如果运行队列已满,将 g 放入全局队列中。
网友评论