Golang Go语言中协程读写上下文变量一直为0
Golang Go语言中协程读写上下文变量一直为0
如代码 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("**************")
}
求大佬指点
更多关于Golang Go语言中协程读写上下文变量一直为0的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你关闭优化跑一遍看看是不是出来了?😂
关闭内联优化 go build -gcflags "-N -l" testX.go
,结果还是一样
<br>func main() {<br> var x int<br> go func(x *int) {<br> for {<br> *x ++<br> }<br> }(&x)<br> time.Sleep(time.Duration(10) * time.Second)<br> fmt.Println("**************")<br> fmt.Println(x)<br> fmt.Println("**************")<br>}<br>
我猜是被编译器优化掉了,把汇编代码输出出来看看
多个线程读写同一个资源加锁吧<br>func main() {<br> var x int64<br> go func() {<br> for {<br> atomic.AddInt64(&x, 1)<br> }<br> }()<br> time.Sleep(10 * time.Second)<br> fmt.Println("**************")<br> fmt.Println(atomic.LoadInt64(&x))<br> fmt.Println("**************")<br>}<br>
嗯,我也想知道编译器如何处理的,数据竞争是确实的,但为什么这样就没有竞争了呢<br>func main() {<br> var x int<br> go func() {<br> for {<br> x++<br> fmt.Println("ddd")<br> }<br> }()<br> time.Sleep(time.Duration(2) * time.Second)<br> fmt.Println("**************")<br> fmt.Println(x)<br> fmt.Println("**************")<br>}<br>
执行上面的代码<br> ~/go/src/awesomeProject/test go run -race testX.go | grep -v 'ddd' <br>**************<br>676626<br>**************<br>
这是未定义的行为。
你要并发操作 x,需要它是原子的或者用 channel 传值,或者加锁。
汇编代码 : https://paste.ubuntu.com/p/67nDFqJXVN/ , 看最下面的 4 行,确实被优化掉了 d.go 11,12 行是 for 和 x++
调用 print 的时候会产生系统资源调用,所以没被优化
我尝试禁用编译优化:go build -gcflags ‘-N’ main.go
发现结果还是 0 !这是啥情况= =;
整个 goroutine 匿名函数被优化掉了
0x0045 00069 (.\t.go:8) MOVQ “”.&x+24(SP), AX
0x004a 00074 (.\t.go:8) INCQ (AX)
都发生 data race 了,程序的行为就是未定义行为,跟输出值是不是 0 有任何关系? 以为输出值不是 0,程序就对了?
两个 goroutine,在两个线程、两个 CPU 上执行,你不对共享内存的读写进行同步操作,A 在写 A 的 cache 里的 x,B 在读 B 的 cache 里的 x,怎么可能有值呢?
操作系统没学好就算了,罚你今天晚上把 Go Memory Model 看三遍。
go 的并发模型了解下。
确实结果一样,不过关闭优化前后打印汇编结果还是有区别的,虽然关闭优化后的 go 出来的函数中还是没有 x++对应的汇编代码(不太能理解的…)。
下面是我的猜测:
在 1.14 之前协程调度是出让式而非抢占式的,如果这段代码在单核机器上运行,就有可能陷入到 for {…}的死循环中而主协程中的代码得不到调度执行,而你 for 循环中加入了 fmt.Pxxx 类的代码就能够使子协程出让执行权。
另外这段代码还有可见性问题,子协程对全局变量的修改,主协程可能是看不到的。
基于上面两个问题的考虑,编译器做出了“错误”优化,导致了你所看到的结果。
被优化了(伊,怎么感觉乖乖的)
你把 // fmt.Println(“ddd”)的注释取消有可以了
楼上说的缓存问题的确值得学习,但应该不是这个问题的原因,10 秒怎么得也刷到 L3 了才对。
看了下汇编,优化成了空循环了,所以应该是优化的问题。
这个问题我之前在 github 上提过 issue,go 的编译器会把这种情况下的修改的代码优化掉的,也只有 golang 会这么做,别的语言只是不保证可见性,但最终一定会读取到新值(并非所有场景都需要完全的一致性性),在 golang 里却永远读不到新值。
可以看这里,同样的问题,可以看汇编代码发现区别 https://www.zhihu.com/question/434964023
在Go语言中,如果你发现协程中读写的上下文(context)变量一直为0,这通常不是context本身的问题,而是由于变量作用域、传递方式或并发控制不当引起的。以下是一些可能的原因和解决方案:
-
变量作用域:确保你在协程外部定义的变量是以正确的方式传递给协程的。如果变量是在协程内部重新声明的,那么它将是一个全新的局部变量,与外部的变量无关。
-
context使用不当:context在Go中主要用于跨API和进程间传递截止日期、取消信号以及其他请求范围的变量。如果你期望在context中存储和读取状态变量,应确保这些变量是线程安全的(例如,使用sync.Mutex或sync.RWMutex保护)。
-
并发控制:在多个协程中读写共享变量时,必须实施适当的并发控制机制。如果没有使用锁(如sync.Mutex)或通道(channel)来同步访问,可能会导致数据竞争和不一致。
-
变量初始化:检查变量是否在传递给协程之前已被正确初始化。未初始化的变量在读取时可能表现为零值。
-
调试和日志:增加日志输出,以跟踪变量的值和协程的执行流程。这有助于诊断问题所在。
总之,确保你理解Go中的并发模型和内存模型,以及正确地使用context和并发控制机制。如果问题依然存在,可以考虑使用Go的race detector工具来检测数据竞争。