一、为什么要无锁
1.1 Mutex的真实成本
-
系统调用/竞争,高并发下可能进入内核态
-
调度干扰,阻塞会触发G/M/P迁移
-
可伸缩性差,核数越多,锁竞争越严重
1.2 无锁适合的典型场景
-
高频读、极低写(统计、计数)
-
状态机/标志位
-
读多写少的配置快照
-
自旋极短的临界区
二、atomic解决的核心问题
atomic本质解决三件事:
-
2.1 原子性:操作不可被打断
-
2.2 可见性:一个goroutine的写,其他goroutine能看到
-
2.3 有序性:禁止CPU/编译器乱序
但是,atomic不解决复杂不变量,那是锁的工作。
三、atomic的能力边界
3.1 atomic能做什么
-
原子读写:Load/Store
-
原子更新:Add/Swap/CompareAndSwap
-
作为内存屏障(Memory Barrier)
3.2 atomic不能做什么
-
不能保护多个变量的一致性
-
不能表达复杂临界区
-
不能替代mutex的条件同步
四、atomic的真实语义:不是“变量”,是“屏障”
4.1 Go内存模型中的关键规则:对同一个变量的atomic Store happens-before 之后的atomic Load,这意味着:
atomic = 发布(release—)+获取(acquire)语义
4.2 最重要的一条原则:
不要把atomic当成“轻量锁”,而要当成“可见性边界”
五、真实场景
5.1 高性能计数器
5.1.1 Mutex版本(参照)
type Counter struct {
mu sync.Mutex
n int64
}
func (c *Counter) Inc() {
c.mu.Lock()
c.n++
c.mu.Unlock()
}
5.1.2 atomic版本
type Counter struct {
n int64
}
func (c *Counter) Inc() {
atomic.AddInt64(&c.n, 1)
}
func (c *Counter) Load() int64 {
return atomic.LoadInt64(&c.n)
}
适用前提:只有“加”和“读”,不涉及其他状态。
5.2 一次性初始化(比sync.Once更底层)
var initialized int32
func Init() {
if atomic.CompareAndSwapInt32(&initialized, 0, 1) {
// 只有一个 goroutine 会进入
doSomeInit()
}
}
内存语义保证:CAS成功=发布初始化结果,之后Load看到1=可见所有初始化写
5.3 配置热更新(读多写少)
5.3.1 使用atomic.Value
var cfg atomic.Value // store *Config
func LoadConfig() *Config {
return cfg.Load().(*Config)
}
func UpdateConfig(c *Config) {
cfg.Store(c)
}
为什么不用RWMutex?
-
读路径零锁、零竞争
-
Store = 完整内存屏障
5.4 状态机/标志位
const (
StateInit = iota
StateRunning
StateStopped
)
var state int32
func Start() bool {
return atomic.CompareAndSwapInt32(&state, StateInit, StateRunning)
}
为什么安全?状态是单变量有限状态机;CAS保证状态跃迁原子性。
六、内存屏障到底在“挡住”什么?
6.1 CPU可能做的事
Store -> Store 乱序
Load -> Load 乱序
Load -> Store 乱序
6.2 atomic 干的事
| 操作 | 屏障语义 |
| Load | Acquire |
| Store | Release |
| CAS/Swap | Full Barrier |
直观理解:atomic = 在这里画一条“所有人都必须停一下”的线
七、常见致命错误
7.1 atomic保护指针字段,但对象内容可变
7.2 用atomic实现复杂临界区
7.3 混用atomic和普通读写,同一变量 要么全atomic,要么全加锁。
八、atomic和Mutex怎么选?
| 场景 | 推荐 |
| 单变量计数/标志 | atomic |
| 多变量一致性 | mutex |
| 配置快照 | atomic.Value |
| 复杂状态机 | mutex |
| 高竞争临界区 | mutex |
九、总结
atomic是并发里的手术刀,不是万能的瑞士军刀;它解决的是可见性与有序性,不是复杂同步;用对场景,性能和可预测性都极强。
哲理:成熟不是控制一切,而是接受不可控,只守住最关键的状态。
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!