Golang中为什么for循环对i++不起作用?

Golang中为什么for循环对i++不起作用?

package main

import (
	"fmt"
	"time"
)

func main() {
	var x int
	go func() {
		for {
			x++
		}
	}()
	time.Sleep(time.Second)
	fmt.Println("x =", x) // x = 0, why?
}

谢谢 🙂

4 回复

time.Sleep() 难道不应该给其他 goroutine 运行的机会吗?

更多关于Golang中为什么for循环对i++不起作用?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我在这里找到了一些解释。

如果只是为了计数,你可以使用原子计数器

因为你的 goroutine 从未有机会运行。在调度器调度它之前,main 函数就已经退出了。你需要某种同步机制,比如通道或 sync.WaitGroup

func main() {
    fmt.Println("hello world")
}

这是因为你的代码中存在数据竞争(data race)。在Go语言中,当多个goroutine并发访问同一个变量,且至少有一个是写操作时,如果没有正确的同步机制,就会发生数据竞争。

在你的例子中:

  1. 主goroutine和匿名goroutine同时访问变量x
  2. 匿名goroutine不断执行x++(写操作)
  3. 主goroutine在1秒后读取x的值
  4. 两个goroutine之间没有使用任何同步机制

问题根源:

  • Go的内存模型不保证一个goroutine的写入对另一个goroutine立即可见
  • 编译器/CPU可能会对指令进行重排序优化
  • 变量x可能被缓存在CPU寄存器中,而不是立即写回内存

正确的解决方案: 使用同步原语来确保可见性和原子性

示例1:使用sync/atomic包(推荐)

package main

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

func main() {
	var x int64 // 必须使用int64,因为atomic.AddInt64需要
	go func() {
		for {
			atomic.AddInt64(&x, 1)
		}
	}()
	time.Sleep(time.Second)
	fmt.Println("x =", atomic.LoadInt64(&x))
}

示例2:使用sync.Mutex

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var (
		x int
		mu sync.Mutex
	)
	
	go func() {
		for {
			mu.Lock()
			x++
			mu.Unlock()
		}
	}()
	
	time.Sleep(time.Second)
	
	mu.Lock()
	fmt.Println("x =", x)
	mu.Unlock()
}

示例3:使用channel进行同步

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	
	go func() {
		x := 0
		for {
			x++
			select {
			case ch <- x:
				// 成功发送
			default:
				// 如果主goroutine还没准备好接收,就继续循环
			}
		}
	}()
	
	time.Sleep(time.Second)
	
	select {
	case x := <-ch:
		fmt.Println("x =", x)
	default:
		fmt.Println("no value received")
	}
}

关键点:

  1. 使用sync/atomic包进行原子操作
  2. 使用sync.Mutexsync.RWMutex进行互斥访问
  3. 使用channel进行goroutine间通信
  4. 运行程序时可以使用go run -race main.go来检测数据竞争

在你的原始代码中,由于没有同步机制,主goroutine可能看到的是x的初始值(0),或者缓存中的旧值,而不是匿名goroutine更新后的值。

回到顶部