1. 先明确:数据竞争的本质
数据竞争是指多个goroutine同时访问同一个共享变量,且至少有一个goroutine是写操作 (读+读无竞争,读+写/写+写有竞争)。Go中可通过 go run -race main.go 检测数据竞争,这是并发Bug的高频来源。
2. 解决数据竞争的核心思路(从"低效到高效"排序)
(1)基础方案:加锁(互斥锁/读写锁)------ 简单但有性能开销
- 适用场景:共享变量读写逻辑复杂、无法用其他方案简化时;
- 优化点:
① 缩小锁粒度:不要用全局大锁,仅对"需要保护的临界区"加锁(如对map的某个key加锁,而非整个map);
② 优先用sync.RWMutex(读多写少场景);
③ 避免锁嵌套(防止死锁)。
(2)Go推荐方案:Channel通信(CSP模型)------ 优雅且并发安全
Go的设计哲学是"不要通过共享内存通信,而要通过通信共享内存",channel本身是并发安全的,通过channel传递数据所有权,从根源避免共享变量。
-
适用场景:goroutine间需要传递数据、且数据流向清晰(如生产者-消费者模型);
-
实战示例:
go// 用channel传递数据,而非共享变量 func main() { ch := make(chan int, 10) // 生产者goroutine go func() { for i := 0; i < 100; i++ { ch <- i // 发送数据,无需加锁 } close(ch) }() // 消费者goroutine go func() { for num := range ch { fmt.Println(num) // 读取数据,无需加锁 } }() time.Sleep(time.Second) } -
注意:channel并非"万能解"------若goroutine间仅需简单的数值更新(如计数器),用channel反而比原子操作更重(有上下文切换开销)。
(3)高效方案:原子操作(sync/atomic)------ 无锁且极致高效
sync/atomic 包提供了对基础类型(int32/int64/uintptr等)的原子操作(加、减、交换、比较并交换),底层通过CPU指令实现,无锁开销,是最简单数据竞争场景的最优解。
-
适用场景:简单数值的并发更新(如计数器、状态标记、连接数统计);
-
实战示例:
goimport "sync/atomic" var count int64 func increment() { atomic.AddInt64(&count, 1) // 原子加1,无数据竞争 } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait() fmt.Println(atomic.LoadInt64(&count)) // 原子读取,保证可见性 } -
注意:原子操作仅支持基础类型,无法用于复杂结构体;且需避免"组合操作"(如先读再写),否则仍可能有逻辑竞争。
(4)进阶方案:无锁编程(基于CAS)------ 高性能场景
利用原子操作的CAS(Compare-And-Swap)指令,实现无锁的数据结构(如无锁队列、无锁map),核心逻辑:
go
// CAS示例:尝试更新值,仅当当前值等于预期值时才更新
ok := atomic.CompareAndSwapInt64(&num, oldVal, newVal)
- 适用场景:高性能中间件(如连接池、消息队列)、对锁开销敏感的核心路径;
- 注意:实现复杂,需处理"ABA问题"(值被修改为A→B→A,CAS误判为未修改),一般业务场景无需自研,优先用成熟库(如
github.com/modern-go/reflect2中的无锁结构)。
(5)规避方案:不可变数据/局部存储
- 不可变数据:共享变量一旦创建就不修改,仅生成新副本(如用
string替代[]byte,string是不可变的),从根源避免写竞争; - Goroutine局部存储(GLS):将共享数据拆分为每个goroutine的局部变量(如每个goroutine独立的数据库连接、缓存实例),无需共享,自然无竞争。
3. 方案选择总结
| 场景 | 最优方案 |
|---|---|
| 简单数值更新(计数器、状态) | sync/atomic 原子操作 |
| goroutine间数据传递 | Channel |
| 复杂共享数据读写(读多写少) | sync.RWMutex |
| 复杂共享数据读写(读写均衡) | sync.Mutex(缩小锁粒度) |
| 高性能无锁场景(中间件) | CAS无锁编程 |
| 可拆分的共享数据 | Goroutine局部存储/不可变数据 |