Go语言不自动防止数据竞争,需显式加锁或用并发安全类型;触发data race的条件是多个goroutine同时读写同一变量且至少一写操作未同步,常见于全局变量、map/切片并发读写、未加锁结构体字段访问。

Go 语言本身不自动防止数据竞争,必须显式加锁或改用并发安全类型,否则 go run -race 一跑就报错。
什么时候会触发 data race?
多个 goroutine 同时读写同一个变量,且至少有一个是写操作,又没做同步 —— 这就是典型 data race。常见于:
- 全局变量被多个 goroutine 修改(比如计数器
counter++) - 切片或 map 被并发读写(
map[string]int不是并发安全的) - 结构体字段被不同 goroutine 独立读写,但结构体本身没加锁
注意:sync.Map 是为「读多写少」场景优化的,不是万能替代品;普通 map 并发写仍会 panic。
用 sync.Mutex 保护共享变量最直接
适用于明确知道哪些字段/变量需要保护、且临界区不长的场景。关键点:
立即学习“go语言免费学习笔记(深入)”;
- 锁粒度要细:只包裹真正需要互斥的操作,别把
fmt.Println或网络调用包进去 - 记得解锁:用
defer mu.Unlock()防止 panic 导致锁未释放 - 不要复制已加锁的结构体:
mu是sync.Mutex类型,它包含不可复制的字段,复制会导致运行时 panic
示例:
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}
用 sync.Atomic 替代简单整数/指针操作
当只对 int32、int64、uint32、uintptr、*T 做原子增减、比较交换时,比 Mutex 更轻量:
- 不需要锁,无 Goroutine 阻塞,性能更高
- 但仅支持基础类型和指针,不能用于结构体字段或复杂逻辑
-
atomic.LoadInt64(&x)和atomic.StoreInt64(&x, v)必须配对使用指针,传值会报错
错误写法:atomic.AddInt64(x, 1) → 编译失败;正确是:atomic.AddInt64(&x, 1)。
channel 比锁更 Go 的通信方式
当多个 goroutine 需要协作完成某件事(比如收集结果、控制生命周期),优先用 channel 传递数据,而不是共享内存:
- 用
chan int收集结果,比用[]int+sync.Mutex更清晰 - 关闭 channel 表示“生产结束”,接收方用
for v := range ch自然退出 - 避免在 channel 上做复杂状态判断;如果逻辑变重,及时拆回 mutex + struct 模式
真正难的是权衡:channel 适合“通信”,mutex 适合“保护状态”。混用时尤其小心——比如一边用 channel 传指针,另一边用 mutex 保护该指针指向的数据,这种嵌套容易漏掉同步点。










