goroutine 排队和调度问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
xiangxihenli
V2EX    Go 编程语言

goroutine 排队和调度问题

  •  
  •   xiangxihenli 2021-12-30 18:27:42 +08:00 2453 次点击
    这是一个创建于 1386 天前的主题,其中的信息可能已经有所发展或是发生改变。
     package main import ( "fmt" "runtime" "time" ) func main() { runtime.GOMAXPROCS(1) num:= 258 for i := 0; i < num; i++ { go func(v int) { fmt.Printf("%d,", v) }(i) } time.Sleep(time.Millisecond * 500) } 

    这段代码,按照我的理解应该是先打印 257,但是 有时候执行会先打印 0(通常是首次编译运行的时候,为了复现,我每次都是删除原编译文件,重新生成,然后执行编译文件),

    这是为什么?

    15 条回复    2022-02-08 16:01:38 +08:00
    Kinnice
        1
    Kinnice  
       2021-12-30 18:34:15 +08:00 via Android
    单核也有抢占啊,不一定是 257 那个抢到了呀
    vxyun
        2
    vxyun  
       2021-12-30 18:56:57 +08:00
    goroutine 不保证顺序的
    meiyoumingzi6
        3
    meiyoumingzi6  
       2021-12-30 19:09:00 +08:00
    道理我都懂, 可是为啥应该 `先打印 257` 嘞
    xiangxihenli
        4
    xiangxihenli  
    OP
       2021-12-30 19:12:06 +08:00
    @vxyun 单核情况下,不是应该先执行 runnext ,然后本地队列,然后全局队列吗?
    vxyun
        5
    vxyun  
       2021-12-30 20:15:14 +08:00
    是的,我本地跑了你的代码,每次第一个输出的都是 257 。我的版本是( go version go1.17.1 windows/amd64 )
    schedule 方法里有一段: 当全局队列有待执行的 goroutine 时,会通过 schedtick 保证有一定几率从全局队列上取 goroutine 来运行。有可能是这个机制导致先输出的 0 ,可以加个 log 看一下。

    if gp == nil {
    // Check the global runnable queue once in a while to ensure fairness.
    // Otherwise two goroutines can completely occupy the local runqueue
    // by constantly respawning each other.
    if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
    lock(&sched.lock)
    gp = globrunqget(_g_.m.p.ptr(), 1)
    unlock(&sched.lock)
    }
    }

    (不知道怎么贴图片,就贴一下代码段,将就下)
    vxyun
        6
    vxyun  
       2021-12-30 20:16:39 +08:00
    @meiyoumingzi6 本地队列长度是 256 ,因为是单核所以会先把所有的 goroutine 加入本地队列然后全局队列,进入 time.sleep 的时候,go 调度器开始工作
    0o0O0o0O0o
        7
    0o0O0o0O0o  
       2021-12-30 21:32:14 +08:00 via iPhone
    你把 fmt.Println 换成更可靠的代码试试,还有重新生成应该加个-a 就行了吧
    Fitz
        8
    Fitz  
       2021-12-31 10:42:55 +08:00
    t/556075 之前问过, 开竞态检测-race 就会不一样
    xiangxihenli
        9
    xiangxihenli  
    OP
       2021-12-31 10:58:22 +08:00
    @Fitz 静态检测 是因为 goroutine 就随机了,runqput 里面,next 直接赋值 false ,所以不是按照先放 runnext 了...这个不是本文要讨论的。
    xiangxihenli
        10
    xiangxihenli  
    OP
       2021-12-31 11:05:03 +08:00
    @Fitz 我看的 runqput 源码,先放 runnext ,runnext 有值的话,把原来的值放到本地队列,本地队列 256 ,如果本地队列已经满了就换搬运前一半到全局队列中。

    执行是,因为是先执行 runnext,所以首次是 257 。
    lysS
        11
    lysS  
       2021-12-31 17:01:09 +08:00
    首先有本地队列 local P, 全局队列 global P, 变量 runnext ,和本地队列最大容量 N (应该是 256 )。入队逻辑:
    先是生成 G0, 然后被放入 runnext ;
    然后 G1 来了,G0 被挤入 local P ,runnext 变成 G1 ;
    。。。。。

    消费时优先从 runnext 开始,
    所以当所有生成 G 的个数 n 小于 N+1 时,打印输出为:n-1, 0, 1, ... n-2

    当生成 G 的个数大于 N+1 时,当 local P 和 runnext 中都占据满了 G 时;下一个 G 来时会触发”溢出操作“:
    将 local P 的前一半放入 global P ,再在 global P后面 append 当前 runnext 中的 P

    此种情况下消费时,还是优先从 runnext 开始,然后 local P ;但是此时 global P 不为空,当连续消费几个本地 G 后,会从 global P 中拿个 G 过来插队。

    因此当生成 G 的个数 n 大于 N+1 时,打印顺序类似(假设只触发了一次溢出操作):n-1, N/2, N/2-1, N/2-2, 0, N/2-3 。。。

    如果不止一个 M, 在消费完本地和全局的 G 后,还会从别的 M 偷 G 过来消费。

    无论怎么说,限制 M 为 1 时,打印顺序应该是一定的。猜测你的打印顺序不相同可能是因为所有 G 还没有创建完成时就在消费了,在 gorountine 延时一下就可以稳定输出。


    ------------------------------------------
    我在电脑上试了下,v1.17.3 ;打印顺序并不符合我的预期,当产生 G 的个数 n 小于等于 257 时,打印输出为:0 ,n-1, n-2,...1 ,很明显对 runnext 不是“挤出”了,而是如果 runnext 不为空就放入 local P 。大于 257 后就更复杂了。
    但是无论怎样,打印输出都是稳定的。
    面试要是问这种题纯属脑瘫,只要大概直到 GMP 是啥就行了。
    kiddingU
        12
    kiddingU  
       2022-01-26 15:08:18 +08:00
    @vxyun 首先 runnext 这个 257 没问题,输出 0 这个确实是 schedule 的机制
    ```go

    if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
    lock(&sched.lock)
    gp = globrunqget(_g_.m.p.ptr(), 1)
    unlock(&sched.lock)
    }

    ```
    每隔 60 次会从 globelq 获取一个执行,打印数据多一点,也可以看到 0 ,1 ,2 ,3 每隔 60 次打印出来
    kiddingU
        13
    kiddingU  
       2022-01-26 15:33:45 +08:00
    @lysS 小于 257 必然是稳定的呀,首先 n-1 ,0,1 2.....n-2, 大于 257 也是稳定的,源码看了就很容易发现了,我的环境也是 go 1.17.3
    lysS
        14
    lysS  
       2022-01-26 17:45:38 +08:00
    @kiddingU 设置 runtime.GOMAXPROCS(1)是,P 的执行顺序都是稳定的
    kiddingU
        15
    kiddingU  
       2022-02-08 16:01:38 +08:00
    @lysS 是的呀~~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1177 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:38 PVG 07:38 LAX 16:38 JFK 19:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86