美文网首页Tips
Go runtime.GOMAXPROCS(1)时发生了什么?

Go runtime.GOMAXPROCS(1)时发生了什么?

作者: Sun东辉 | 来源:发表于2022-05-25 07:18 被阅读0次

    首先,看一段有趣的代码:

    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))
    }
    
    

    runtime 地址切换的过程

    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 放入全局队列中。

    相关文章

      网友评论

        本文标题:Go runtime.GOMAXPROCS(1)时发生了什么?

        本文链接:https://www.haomeiwen.com/subject/gxhdprtx.html