Go 无锁并发实战:用 sync/atomic 与 atomic.Pointer 实现计数器与配置热更新
互斥锁不是并发的唯一答案
一说到共享状态,很多人的第一反应是加 sync.Mutex。锁能解决所有竞态,但代价是串行化:同一时刻只有一个 goroutine 能进临界区,高并发下容易成为瓶颈。当操作只是"读一个数、加一、写回"这种简单动作时,CPU 提供了原子的读改写指令,Go 的 sync/atomic 包把它封装成了无锁接口,既快又不会阻塞。
原子计数器:比 Mutex 更轻
atomic.Int64 自带 Load、Add、Store,底层是一条 CPU 原子指令,不需要进锁。下面是 1000 个 goroutine 各加一次,最后必然得到 1000。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type Counter struct {
v atomic.Int64
}
func (c *Counter) Inc() int64 { return c.v.Add(1) }
func (c *Counter) Load() int64 { return c.v.Load() }
func main() {
var c Counter
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Inc()
}()
}
wg.Wait()
fmt.Println(c.Load()) // 1000
}
如果用 Mutex 包一个 int64,结果一样正确,但每次 Inc 都要抢锁释放锁。计数器这种高频操作,原子类型能明显少掉锁的开销。
atomic.Pointer:无锁换配置
配置热更新是个经典场景:后台 goroutine 读到了新配置,要替换掉正在被大量请求读取的旧配置。如果每次读都加锁,读多写少时锁几乎都花在读者互相让路上。atomic.Pointer 让写端一次性原子替换整个指针,读端永远拿到一个完整一致的快照,全程无锁。
package main
import (
"fmt"
"sync/atomic"
)
type Config struct {
TimeoutMs int
Feature bool
}
var cfg atomic.Pointer[Config]
func init() {
cfg.Store(&Config{TimeoutMs: 500, Feature: false})
}
func reload() {
// 原子替换,读者无锁,且永远看不到写到一半的结构
cfg.Store(&Config{TimeoutMs: 800, Feature: true})
}
func handle() Config {
return *cfg.Load() // 取一份完整快照,后续用不会读到半旧半新
}
func main() {
before := handle()
reload()
after := handle()
fmt.Println(before.TimeoutMs, after.TimeoutMs)
}
为什么这样不会读到"撕裂"的值
指针替换是一个机器字宽的原子写,要么旧指针要么新指针,不存在中间态。读端拿到指针后解引用,得到的要么是旧对象要么是全新对象,两个对象本身都是初始化好的完整值。这和"先改字段 A 再改字段 B"的普通写不同,后者在并发读时可能看到只改了一半的结构。
使用边界
- atomic 适合单一变量的读改写和指针整体替换;一旦要同时保护多个字段的一致性,还是得用 Mutex。
- atomic.Pointer 存的是指针,读端解引用后得到的对象不应再被写端原地修改,否则引入新的竞态;正确做法是写端构造新对象再整体替换。
- Go 1.19 之后推荐用 atomic.Int64、atomic.Bool 这类类型安全封装,比老式的 atomic.AddInt64(&x, 1) 更不容易用错地址。
- 无锁不等于无成本,它只是把成本从锁的等待转移到了缓存一致性流量上;在"一个变量高频读写"的场景收益最大。
小结
并发不只有加锁一条路。sync/atomic 的整型类型适合计数器、标志位这类单值高频操作;atomic.Pointer 适合读多写少、需要整体替换的配置或缓存对象。把对的工具用在对的场景,既保住正确性,又把锁竞争这座大山搬走。
test1
文章作者
互斥锁不是并发的唯一答案 一说到共享状态,很多人的第一反应是加 sync.Mutex。锁能解决所有竞态,但代价是串行化...
- 分类
- 技术
- 发布时间
- 2026-10-07
- 字数
- 约 1921 字
- 阅读
- 3 次
