
go语言不会隐式保护共享变量,上述代码存在数据竞争(data race),看似总输出100只是侥幸;实际行为未定义,需显式同步(如互斥锁、原子操作或channel)才能保证并发安全。
go语言不会隐式保护共享变量,上述代码存在数据竞争(data race),看似总输出100只是侥幸;实际行为未定义,需显式同步(如互斥锁、原子操作或channel)才能保证并发安全。
在Go中,m++ 并非原子操作——它本质上是“读取 m → 加 1 → 写回 m”三步的组合。当多个 goroutine 并发执行 add() 时,若两个 goroutine 同时读到 m = 5,各自加 1 后都写回 6,则一次自增被丢失。这种数据竞争(data race) 是典型的并发缺陷,其结果不可预测:可能输出 97、99、100,甚至因编译器优化或调度差异而每次不同。
你观察到“总是输出 100”,这并非 Go 的保护机制所致,而是偶然性掩盖了问题:在轻量级测试(100 次 goroutine、短生命周期、无争抢压力)下,调度器可能让 goroutine 串行执行,或硬件/内存模型暂时“隐藏”了竞争。但一旦负载增加、环境变化(如启用 -race、更换CPU、升级Go版本),问题必然暴露:
$ go run -race main.go WARNING: DATA RACE Read at 0x00... by goroutine 7 Previous write at 0x00... by goroutine 6 Found 1 data race(s)
✅ 正确做法:使用显式同步原语。以下是三种推荐方案:
1. 使用 sync.Mutex(适合复杂逻辑)
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"fmt"
"sync"
"time"
)
var (
m int
mutex sync.Mutex
)
func add() {
mutex.Lock()
m++
mutex.Unlock()
}
func main() {
m = 0
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
add()
}()
}
wg.Wait()
fmt.Println(m) // 稳定输出 100
}2. 使用 sync/atomic(高性能、仅基础类型)
import "sync/atomic"
var m int64 // 注意:atomic 操作要求类型严格匹配
func add() {
atomic.AddInt64(&m, 1)
}
// ... 主函数中用 atomic.LoadInt64(&m) 读取3. 使用 channel(符合Go信道优先哲学)
func main() {
ch := make(chan int, 100)
for i := 0; i < 100; i++ {
go func() { ch <- 1 }()
}
m := 0
for i := 0; i < 100; i++ {
m += <-ch
}
fmt.Println(m)
}⚠️ 关键注意事项:
- 永远不要依赖“看起来正常”的并发代码——未定义行为可能在生产环境突然爆发;
- 开发阶段务必启用
go run -race或go test -race检测数据竞争; - 避免全局变量共享状态,优先通过参数传递或 channel 通信;
-
time.Sleep不是同步手段,它不可靠且难以维护,应使用sync.WaitGroup等显式等待机制。
Go 的并发模型强调“通过通信共享内存”,而非“通过共享内存通信”。真正的并发安全,源于设计时的显式同步意识,而非语言的隐式兜底。


















