原子操作
问题
Go 的 sync/atomic 包提供了什么功能?原子操作和 Mutex 有什么区别?
答案
什么是原子操作
原子操作是不可中断的最小操作单元,在执行过程中不会被其他 goroutine 打断。底层通过 CPU 指令(如 CAS)实现,不需要锁,比 Mutex 性能更高。
基本操作
import "sync/atomic"
var counter int64
// 原子加
atomic.AddInt64(&counter, 1) // counter++
atomic.AddInt64(&counter, -1) // counter--
// 原子读写
val := atomic.LoadInt64(&counter) // 原子读
atomic.StoreInt64(&counter, 100) // 原子写
// CAS(Compare-And-Swap)
// 如果 counter == 100,则设置为 200,返回 true;否则不修改,返回 false
swapped := atomic.CompareAndSwapInt64(&counter, 100, 200)
// 原子交换
old := atomic.SwapInt64(&counter, 300) // 设置新值,返回旧值
atomic.Value——原子读写任意类型
var config atomic.Value
// 存储(任意类型,但后续 Store 必须相同类型)
config.Store(map[string]string{
"host": "localhost",
"port": "8080",
})
// 读取
cfg := config.Load().(map[string]string)
fmt.Println(cfg["host"]) // localhost
atomic.Value 典型用途
用于读多写少的配置热加载场景:
- 配置文件变更时
Store新配置 - 业务代码
Load读取配置 - 不需要加锁,高并发下性能极好
Go 1.19+: 泛型原子类型
// atomic.Int64(替代 atomic.AddInt64 等函数式 API)
var counter atomic.Int64
counter.Add(1)
counter.Store(100)
val := counter.Load()
counter.CompareAndSwap(100, 200)
// atomic.Bool
var flag atomic.Bool
flag.Store(true)
if flag.Load() { /* ... */ }
// atomic.Pointer[T](替代 atomic.Value 的类型安全版本)
type Config struct {
Host string
Port int
}
var cfgPtr atomic.Pointer[Config]
cfgPtr.Store(&Config{Host: "localhost", Port: 8080})
cfg := cfgPtr.Load()
fmt.Println(cfg.Host)
CAS(Compare-And-Swap)实现无锁算法
// 用 CAS 实现无锁计数器
func casIncrement(addr *int64) {
for {
old := atomic.LoadInt64(addr)
if atomic.CompareAndSwapInt64(addr, old, old+1) {
return // CAS 成功
}
// CAS 失败(被其他 goroutine 修改了),自旋重试
}
}
Atomic vs Mutex
| 维度 | atomic | Mutex |
|---|---|---|
| 实现 | CPU 指令(硬件级) | 操作系统信号量(软件级) |
| 性能 | 极高(无上下文切换) | 较低(可能触发 goroutine 调度) |
| 适用范围 | 简单的读/写/CAS 操作 | 复杂的临界区(多步操作) |
| 复杂度 | 低(API 简单) | 中(需要管理锁的粒度) |
| 饥饿 | CAS 自旋可能饥饿 | 公平锁可避免 |
选择建议:
- 单变量的简单操作(计数器、标志位、配置指针)→
atomic - 多变量或多步骤的复合操作 →
Mutex
常见面试问题
Q1: atomic 操作为什么比 Mutex 快?
答案:
atomic直接使用 CPU 指令,不涉及 goroutine 调度和上下文切换Mutex在竞争时会将 goroutine 挂起,涉及运行时调度开销- 但
atomic只能操作单个变量,无法保护多步操作的原子性
Q2: CAS 有什么问题?
答案:
- ABA 问题:值从 A→B→A,CAS 认为没变。Go 通常不会遇到这个问题(没有指针复用),但在某些场景需注意
- 自旋开销:高竞争下 CAS 不断重试,浪费 CPU
- 只能操作单个变量:无法原子地操作多个变量
Q3: atomic.Value 有什么限制?
答案:
- 首次
Store后,后续Store的值类型必须相同(否则 panic) Store不能存nil(panic)- 适合读多写少,不适合高频写入