Golang调试与运行结果不一致的奇怪现象

Golang调试与运行结果不一致的奇怪现象

package main

import (
	"fmt"
)

var x int32 = 1

func main() {
	go foo()
	for {
		fmt.Printf("%d\n", x)
	}
}

func foo() {
	for i := 0; ; i++ {
		if i%2 == 0 {
			x = 2
		} else {
			x = 3
		}
	}
}

image


更多关于Golang调试与运行结果不一致的奇怪现象的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

非常感谢您详细的回答

更多关于Golang调试与运行结果不一致的奇怪现象的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你在并行运行两个紧密循环。执行顺序是任意的,每次都可能改变。如果两者由不同的系统线程执行,操作系统调度器将决定执行的顺序和优先级。它们可能在不同的核心上运行,也可能在同一个核心上运行。

从两个并发的 Go 协程并发访问同一个变量是不安全的——如果你使用 -race 标志运行代码,你会收到一个警告。

由于对 X 的访问没有安全的“前/后”顺序,编译器可能会进行优化,并将 X 的值作为循环不变量在本地缓存——因此循环会假设 X 永远保持为 1,从而永远只打印 1(这对于规范来说是正确的行为)。

我认为调试器可能不会使用这种优化,因此你“幸运地”在输出中得到了一个随机的 2/3 字符串。

这是一个典型的并发数据竞争问题。在Go语言中,当多个goroutine同时读写共享变量而没有同步机制时,就会出现这种调试和运行结果不一致的现象。

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

// 方案1: 使用原子操作
var x int32 = 1

func main() {
	go foo()
	for {
		fmt.Printf("%d\n", atomic.LoadInt32(&x))
	}
}

func foo() {
	for i := 0; ; i++ {
		if i%2 == 0 {
			atomic.StoreInt32(&x, 2)
		} else {
			atomic.StoreInt32(&x, 3)
		}
	}
}
package main

import (
	"fmt"
	"sync"
)

// 方案2: 使用互斥锁
var (
	x  int32 = 1
	mu sync.RWMutex
)

func main() {
	go foo()
	for {
		mu.RLock()
		fmt.Printf("%d\n", x)
		mu.RUnlock()
	}
}

func foo() {
	for i := 0; ; i++ {
		mu.Lock()
		if i%2 == 0 {
			x = 2
		} else {
			x = 3
		}
		mu.Unlock()
	}
}
package main

import (
	"fmt"
)

// 方案3: 使用通道
func main() {
	ch := make(chan int32)
	
	go foo(ch)
	
	for val := range ch {
		fmt.Printf("%d\n", val)
	}
}

func foo(ch chan<- int32) {
	for i := 0; ; i++ {
		if i%2 == 0 {
			ch <- 2
		} else {
			ch <- 3
		}
	}
}

使用go run -race命令可以检测数据竞争:

go run -race main.go

编译器优化和CPU缓存一致性可能导致不同运行环境下看到不同的值。在调试器中,执行速度的差异可能暂时掩盖了数据竞争,但在实际运行时问题就会暴露出来。

回到顶部