
如代码 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("**************") } 求大佬指点
1 lozzow 2021-03-20 16:09:52 +08:00 标记等个答案 |
2 Orlion 2021-03-20 16:21:17 +08:00 via Android 你关闭优化跑一遍看看是不是出来了? |
3 xuletter2021 OP 关闭内联优化 ```go build -gcflags "-N -l" testX.go```,结果还是一样 |
4 darrh00 2021-03-20 16:34:52 +08:00 代码读写 x 变量有 data race, go build -race 后再跑一遍就知道原因了。 |
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("**************") } ``` |
6 carlclone 2021-03-20 17:01:15 +08:00 我猜是被编译器优化掉了,把汇编代码输出出来看看 |
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("**************") } ``` |
8 xuletter2021 OP @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 ************** ``` |
9 dreasky 2021-03-20 17:22:14 +08:00 |
10 whee1 2021-03-20 17:22:56 +08:00 这是未定义的行为。 你要并发操作 x,需要它是原子的或者用 channel 传值,或者加锁。 |
11 carlclone 2021-03-20 17:23:09 +08:00 汇编代码 : https://paste.ubuntu.com/p/67nDFqJXVN/ , 看最下面的 4 行,确实被优化掉了 d.go 11,12 行是 for 和 x++ |
12 777777 2021-03-20 17:24:01 +08:00 调用 print 的时候会产生系统资源调用,所以没被优化 |
13 jasonkayzk 2021-03-20 17:28:50 +08:00 @carlclone 我尝试禁用编译优化:go build -gcflags '-N' main.go 发现结果还是 0 !这是啥情况= =; |
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) |
15 darrh00 2021-03-20 18:08:48 +08:00 都发生 data race 了,程序的行为就是未定义行为,跟输出值是不是 0 有任何关系? 以为输出值不是 0,程序就对了? |
16 RedBlackTree 2021-03-20 18:12:16 +08:00 两个 goroutine,在两个线程、两个 CPU 上执行,你不对共享内存的读写进行同步操作,A 在写 A 的 cache 里的 x,B 在读 B 的 cache 里的 x,怎么可能有值呢? |
17 RedBlackTree 2021-03-20 18:13:19 +08:00 操作系统没学好就算了,罚你今天晚上把 Go Memory Model 看三遍。 |
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) } |
19 Linxing 2021-03-20 18:22:09 +08:00 go 的并发模型了解下。 |
20 Orlion 2021-03-20 18:25:44 +08:00 @xuletter2021 确实结果一样,不过关闭优化前后打印汇编结果还是有区别的,虽然关闭优化后的 go 出来的函数中还是没有 x++对应的汇编代码(不太能理解的...)。 下面是我的猜测: 在 1.14 之前协程调度是出让式而非抢占式的,如果这段代码在单核机器上运行,就有可能陷入到 for {...}的死循环中而主协程中的代码得不到调度执行,而你 for 循环中加入了 fmt.Pxxx 类的代码就能够使子协程出让执行权。 另外这段代码还有可见性问题,子协程对全局变量的修改,主协程可能是看不到的。 基于上面两个问题的考虑,编译器做出了“错误”优化,导致了你所看到的结果。 |
21 lysS 2021-03-20 19:56:46 +08:00 被优化了(伊,怎么感觉乖乖的) 你把 // fmt.Println("ddd")的注释取消有可以了 |
22 lewinlan 2021-03-20 20:06:58 +08:00 via Android 楼上说的缓存问题的确值得学习,但应该不是这个问题的原因,10 秒怎么得也刷到 L3 了才对。 看了下汇编,优化成了空循环了,所以应该是优化的问题。 |
23 xfriday 2021-03-20 22:46:32 +08:00 这个问题我之前在 github 上提过 issue,go 的编译器会把这种情况下的修改的代码优化掉的,也只有 golang 会这么做,别的语言只是不保证可见性,但最终一定会读取到新值(并非所有场景都需要完全的一致性性),在 golang 里却永远读不到新值。 |
24 sikasjc 2021-03-22 11:35:37 +08:00 可以看这里,同样的问题,可以看汇编代码发现区别 https://www.zhihu.com/question/434964023 |