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

25 回复

标记等个答案

更多关于Golang Go语言中协程读写上下文变量一直为0的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你关闭优化跑一遍看看是不是出来了?😂

关闭内联优化 go build -gcflags "-N -l" testX.go,结果还是一样

代码读写 x 变量有 data race, go build -race 后再跑一遍就知道原因了。

<br>func main() {<br> var x int<br> go func(x *int) {<br> for {<br> *x ++<br> }<br> }(&amp;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(&amp;x, 1)<br> }<br> }()<br> time.Sleep(10 * time.Second)<br> fmt.Println("**************")<br> fmt.Println(atomic.LoadInt64(&amp;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 看三遍。

package main

import (
“fmt”
“time”
)

func main() {
var x = 0

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

time.Sleep(time.Second * 3)
fmt.Print(x)
}

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本身的问题,而是由于变量作用域、传递方式或并发控制不当引起的。以下是一些可能的原因和解决方案:

  1. 变量作用域:确保你在协程外部定义的变量是以正确的方式传递给协程的。如果变量是在协程内部重新声明的,那么它将是一个全新的局部变量,与外部的变量无关。

  2. context使用不当:context在Go中主要用于跨API和进程间传递截止日期、取消信号以及其他请求范围的变量。如果你期望在context中存储和读取状态变量,应确保这些变量是线程安全的(例如,使用sync.Mutex或sync.RWMutex保护)。

  3. 并发控制:在多个协程中读写共享变量时,必须实施适当的并发控制机制。如果没有使用锁(如sync.Mutex)或通道(channel)来同步访问,可能会导致数据竞争和不一致。

  4. 变量初始化:检查变量是否在传递给协程之前已被正确初始化。未初始化的变量在读取时可能表现为零值。

  5. 调试和日志:增加日志输出,以跟踪变量的值和协程的执行流程。这有助于诊断问题所在。

总之,确保你理解Go中的并发模型和内存模型,以及正确地使用context和并发控制机制。如果问题依然存在,可以考虑使用Go的race detector工具来检测数据竞争。

回到顶部