golang 协程读写上下文变量 一直为 0 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
xuletter2021
V2EX    Go 编程语言

golang 协程读写上下文变量 一直为 0

  •  
  •   xuletter2021 2021-03-20 15:28:26 +08:00 3526 次点击
    这是一个创建于 1698 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如代码 1:运行过程中无论 sleep 多久,输出都是 0

    func main() { var x int go func() { for { x++ } }() time.Sleep(time.Duration(10) * time.Second) fmt.Println("**************") fmt.Println(x) fmt.Println("**************") } 

    如代码 2:在上面第六行加了点代码就输出就变了,一直不明白为什么

    func main() { var x int go func() { for { x++ // select() // or // fmt.Println("ddd") } }() time.Sleep(time.Duration(1) * time.Second) fmt.Println("**************") fmt.Println(x) fmt.Println("**************") } 

    求大佬指点

    24 条回复    2021-03-22 11:35:37 +08:00
    lozzow
        1
    lozzow  
       2021-03-20 16:09:52 +08:00
    标记等个答案
    Orlion
        2
    Orlion  
       2021-03-20 16:21:17 +08:00 via Android
    你关闭优化跑一遍看看是不是出来了?
    xuletter2021
        3
    xuletter2021  
    OP
       2021-03-20 16:27:24 +08:00
    关闭内联优化 ```go build -gcflags "-N -l" testX.go```,结果还是一样
    darrh00
        4
    darrh00  
       2021-03-20 16:34:52 +08:00
    代码读写 x 变量有 data race, go build -race 后再跑一遍就知道原因了。
    plantparknet
        5
    plantparknet  
       2021-03-20 16:51:47 +08:00
    ```
    func main() {
    var x int
    go func(x *int) {
    for {
    *x ++
    }
    }(&x)
    time.Sleep(time.Duration(10) * time.Second)
    fmt.Println("**************")
    fmt.Println(x)
    fmt.Println("**************")
    }
    ```
    carlclone
        6
    carlclone  
       2021-03-20 17:01:15 +08:00
    我猜是被编译器优化掉了,把汇编代码输出出来看看
    dreasky
        7
    dreasky  
       2021-03-20 17:05:15 +08:00
    多个线程读写同一个资源加锁吧
    ```
    func main() {
    var x int64
    go func() {
    for {
    atomic.AddInt64(&x, 1)
    }
    }()
    time.Sleep(10 * time.Second)
    fmt.Println("**************")
    fmt.Println(atomic.LoadInt64(&x))
    fmt.Println("**************")
    }
    ```
    xuletter2021
        8
    xuletter2021  
    OP
       2021-03-20 17:16:05 +08:00
    @carlclone 嗯,我也想知道编译器如何处理的,数据竞争是确实的,但为什么这样就没有竞争了呢
    ```
    func main() {
    var x int
    go func() {
    for {
    x++
    fmt.Println("ddd")
    }
    }()
    time.Sleep(time.Duration(2) * time.Second)
    fmt.Println("**************")
    fmt.Println(x)
    fmt.Println("**************")
    }
    ```

    执行上面的代码
    ```
    ~/go/src/awesomeProject/test go run -race testX.go | grep -v 'ddd'
    **************
    676626
    **************
    ```
    dreasky
        9
    dreasky  
       2021-03-20 17:22:14 +08:00   1
    whee1
        10
    whee1  
       2021-03-20 17:22:56 +08:00   1
    这是未定义的行为。
    你要并发操作 x,需要它是原子的或者用 channel 传值,或者加锁。
    carlclone
        11
    carlclone  
       2021-03-20 17:23:09 +08:00
    汇编代码 : https://paste.ubuntu.com/p/67nDFqJXVN/ , 看最下面的 4 行,确实被优化掉了 d.go 11,12 行是 for 和 x++
    777777
        12
    777777  
       2021-03-20 17:24:01 +08:00
    调用 print 的时候会产生系统资源调用,所以没被优化
    jasonkayzk
        13
    jasonkayzk  
       2021-03-20 17:28:50 +08:00
    @carlclone 我尝试禁用编译优化:go build -gcflags '-N' main.go
    发现结果还是 0 !这是啥情况= =;
    whoami9894
        14
    whoami9894  
       2021-03-20 18:03:24 +08:00
    整个 goroutine 匿名函数被优化掉了
    0x0045 00069 (.\t.go:8) MOVQ "".&x+24(SP), AX
    0x004a 00074 (.\t.go:8) INCQ (AX)
    darrh00
        15
    darrh00  
       2021-03-20 18:08:48 +08:00
    都发生 data race 了,程序的行为就是未定义行为,跟输出值是不是 0 有任何关系? 以为输出值不是 0,程序就对了?
    RedBlackTree
        16
    RedBlackTree  
       2021-03-20 18:12:16 +08:00
    两个 goroutine,在两个线程、两个 CPU 上执行,你不对共享内存的读写进行同步操作,A 在写 A 的 cache 里的 x,B 在读 B 的 cache 里的 x,怎么可能有值呢?
    RedBlackTree
        17
    RedBlackTree  
       2021-03-20 18:13:19 +08:00
    操作系统没学好就算了,罚你今天晚上把 Go Memory Model 看三遍。
    treblex
        18
    treblex  
       2021-03-20 18:20:12 +08:00
    package main

    import (
    "fmt"
    "time"
    )

    func main() {
    var x = 0

    go func(_x *int) {
    *_x++
    }(&x)

    time.Sleep(time.Second * 3)
    fmt.Print(x)
    }
    Linxing
        19
    Linxing  
       2021-03-20 18:22:09 +08:00
    go 的并发模型了解下。
    Orlion
        20
    Orlion  
       2021-03-20 18:25:44 +08:00
    @xuletter2021 确实结果一样,不过关闭优化前后打印汇编结果还是有区别的,虽然关闭优化后的 go 出来的函数中还是没有 x++对应的汇编代码(不太能理解的...)。

    下面是我的猜测:

    在 1.14 之前协程调度是出让式而非抢占式的,如果这段代码在单核机器上运行,就有可能陷入到 for {...}的死循环中而主协程中的代码得不到调度执行,而你 for 循环中加入了 fmt.Pxxx 类的代码就能够使子协程出让执行权。

    另外这段代码还有可见性问题,子协程对全局变量的修改,主协程可能是看不到的。

    基于上面两个问题的考虑,编译器做出了“错误”优化,导致了你所看到的结果。
    lysS
        21
    lysS  
       2021-03-20 19:56:46 +08:00
    被优化了(伊,怎么感觉乖乖的)
    你把 // fmt.Println("ddd")的注释取消有可以了
    lewinlan
        22
    lewinlan  
       2021-03-20 20:06:58 +08:00 via Android
    楼上说的缓存问题的确值得学习,但应该不是这个问题的原因,10 秒怎么得也刷到 L3 了才对。
    看了下汇编,优化成了空循环了,所以应该是优化的问题。
    xfriday
        23
    xfriday  
       2021-03-20 22:46:32 +08:00
    这个问题我之前在 github 上提过 issue,go 的编译器会把这种情况下的修改的代码优化掉的,也只有 golang 会这么做,别的语言只是不保证可见性,但最终一定会读取到新值(并非所有场景都需要完全的一致性性),在 golang 里却永远读不到新值。
    sikasjc
        24
    sikasjc  
       2021-03-22 11:35:37 +08:00
    可以看这里,同样的问题,可以看汇编代码发现区别 https://www.zhihu.com/question/434964023
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3188 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 88ms UTC 11:10 PVG 19:10 LAX 03:10 JFK 06:10
    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