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

2021-03-20 15:28:26 +08:00
 xuletter2021

如代码 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("**************")
}

求大佬指点

2898 次点击
所在节点    Go 编程语言
24 条回复
lozzow
2021-03-20 16:09:52 +08:00
标记等个答案
Orlion
2021-03-20 16:21:17 +08:00
你关闭优化跑一遍看看是不是出来了?😂
xuletter2021
2021-03-20 16:27:24 +08:00
关闭内联优化 ```go build -gcflags "-N -l" testX.go```,结果还是一样
darrh00
2021-03-20 16:34:52 +08:00
代码读写 x 变量有 data race, go build -race 后再跑一遍就知道原因了。
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
2021-03-20 17:01:15 +08:00
我猜是被编译器优化掉了,把汇编代码输出出来看看
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
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
2021-03-20 17:22:14 +08:00
whee1
2021-03-20 17:22:56 +08:00
这是未定义的行为。
你要并发操作 x,需要它是原子的或者用 channel 传值,或者加锁。
carlclone
2021-03-20 17:23:09 +08:00
汇编代码 : https://paste.ubuntu.com/p/67nDFqJXVN/ , 看最下面的 4 行,确实被优化掉了 d.go 11,12 行是 for 和 x++
777777
2021-03-20 17:24:01 +08:00
调用 print 的时候会产生系统资源调用,所以没被优化
jasonkayzk
2021-03-20 17:28:50 +08:00
@carlclone 我尝试禁用编译优化:go build -gcflags '-N' main.go
发现结果还是 0 !这是啥情况= =;
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
2021-03-20 18:08:48 +08:00
都发生 data race 了,程序的行为就是未定义行为,跟输出值是不是 0 有任何关系? 以为输出值不是 0,程序就对了?
RedBlackTree
2021-03-20 18:12:16 +08:00
两个 goroutine,在两个线程、两个 CPU 上执行,你不对共享内存的读写进行同步操作,A 在写 A 的 cache 里的 x,B 在读 B 的 cache 里的 x,怎么可能有值呢?
RedBlackTree
2021-03-20 18:13:19 +08:00
操作系统没学好就算了,罚你今天晚上把 Go Memory Model 看三遍。
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
2021-03-20 18:22:09 +08:00
go 的并发模型了解下。
Orlion
2021-03-20 18:25:44 +08:00
@xuletter2021 确实结果一样,不过关闭优化前后打印汇编结果还是有区别的,虽然关闭优化后的 go 出来的函数中还是没有 x++对应的汇编代码(不太能理解的...)。

下面是我的猜测:

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

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

基于上面两个问题的考虑,编译器做出了“错误”优化,导致了你所看到的结果。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/763452

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX