美文网首页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)时发生了什么?

    首先,看一段有趣的代码: 结果: 这里考察的是单线程情况下调度的过程。你可以多次尝试,每次的结果都是如此,毫无例外...

  • go runtime包的使用

    runtime.GOMAXPROCS函数 通过runtime.GOMAXPROCS函数,应用程序何以在运行期间设置...

  • 两个协程交替输出1-20

    原理:runtime.GOMAXPROCS(1)设置单核,runtime.Gosched()让出时间片

  • Go 1.19 是未来

    12 年前,Go诞生了。从本文中,您将了解 Go 语言的现状,以及最近围绕该语言发生的事情。 什么是Go? 在我们...

  • go环境以及kubernetes的go-client介绍

    1、go语言介绍: (1)Go语言是谷歌2009发布的第二款开源编程语言。 (2)Go语言专门针对多处理器系统应用...

  • 当runtime.GOMAXPROCS(1)时多个协程的执行顺序

    网上有一道关于多个协程的执行顺序的题目。 下面的代码会输出什么,并说明原因 这道题的参考答案是:“打印的顺序是随机...

  • 加入007发生了什么

    因为想要的太多,想去的地方太远,想买的东西很贵! 14号开班以来,个人信息很滞后,以至于总是爬楼听或看,有时多了真...

  • 11月发生了什么

    11月,妹妹结婚,新郎在台上唱着唱着就哭了。我在台下,听着妹妹的录音,也忍不住哭了好几次,一包纸巾用了一半才停住。...

  • 1776年发生了什么?

    From Santi's earthly tomb with demon's hole, 'Cross Rome ...

  • 1938年发生了什么

    1938年的历史上发生了什么事情? 1938年加拿大著名的外科医生白求恩率领医疗团队来华支援中国人民的抗日战争。 ...

网友评论

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

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