[Go 编程语言] 诡异的执行结果,有哪位 Go 大神来给瞧瞧?

先说一下具体背景,本人在刷题,有一道题是要求使用三个协程依次循环输出 ABCABCABCABCABC 。

以下这种实现方式会出现非常诡异的结果:

package main import ( "fmt" "sync"
) func main() { var wg sync.WaitGroup = sync.WaitGroup{} wg.Add(1) // var ch chan bool = make(chan bool) var i int = 0 go func() { for { // 自旋锁 for i%3 != 0 { } fmt.Print("A", i) i = i + 1 } }() go func() { for { // 自旋锁 for i%3 != 1 { } fmt.Print("B", i) i = i + 1 } }() go func() { for { // 限制循环次数,避免一直死循环 if i >= 3 { fmt.Print("E", i, "\n") i = 2 break } // 这段如果注释掉,就只会输出 AB 然后一直死循环 fmt.Print("[K]") // 自旋锁 for i%3 != 2 { } fmt.Print("C", i) i++ } wg.Done() }() // ch <- true wg.Wait()
} 

上面三个协程使用一个变量来模拟锁,当变量的值和自身对应,即和 3 取余后比较与第 N (取 0 、1 、2 )个协程相等,就说明该协程获取到锁,于是输出对应的字母,然后通过将变量的值增加的方式来模拟释放锁。

如果直接运行上面那段代码,有时候会输出

[K]A0B1C2E3
A3A3B4

为了方便查找问题,在输出字母的时候也会同时输出 i 的值,可以看到有两个 A3 ,问题是每次协程输出字母后 i 的值都会自增,理论上不可能出现两个 A3 ,但显示就是这么诡异。

还有,代码注释里面又说到,如果把 fmt.Print("[K]"),注释掉,就只会输出 A0B1 ,然后一直陷入死循环。真实诡异!

这还没完,如果把 if i >= 3 { 这段用来限制循环次数的代码放到 fmt.Print("C", i) 下面,那一切又恢复正常了。负负得正?诡异的诡异为正常?

本人的 Go 版本为 1.18.1 ,切换到 1.14.15 也是有同样的问题。

个人猜测是 i = i + 1 的问题,于是在 i = i + 1 后也再输出 i 的值,发现 i 的值并有增加,这样看来确实是它的问题,问题这没道理啊!虽说三个协程存在并发问题,但在操作 i 时只有一个协程在操作,其它都是在读,不应该会影响才对。难道真的有影响?一个协程把 i 拿出来,加一后再放回去,这个拿出来是赋值给寄存器,寄存器加一后再拷贝到栈中,这个过程另一协程也会去读,同样把值赋值给寄存器,这个寄存器是一样的?共享的?所以就被覆盖了?感觉有这个可能。