如代码 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 1
|
10
whee1 2021-03-20 17:22:56 +08:00 1
这是未定义的行为。
你要并发操作 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
|