跳到主要内容

原子操作

问题

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

维度atomicMutex
实现CPU 指令(硬件级)操作系统信号量(软件级)
性能极高(无上下文切换)较低(可能触发 goroutine 调度)
适用范围简单的读/写/CAS 操作复杂的临界区(多步操作)
复杂度低(API 简单)中(需要管理锁的粒度)
饥饿CAS 自旋可能饥饿公平锁可避免

选择建议:

  • 单变量的简单操作(计数器、标志位、配置指针)→ atomic
  • 多变量或多步骤的复合操作Mutex

常见面试问题

Q1: atomic 操作为什么比 Mutex 快?

答案

  • atomic 直接使用 CPU 指令,不涉及 goroutine 调度和上下文切换
  • Mutex 在竞争时会将 goroutine 挂起,涉及运行时调度开销
  • atomic 只能操作单个变量,无法保护多步操作的原子性

Q2: CAS 有什么问题?

答案

  1. ABA 问题:值从 A→B→A,CAS 认为没变。Go 通常不会遇到这个问题(没有指针复用),但在某些场景需注意
  2. 自旋开销:高竞争下 CAS 不断重试,浪费 CPU
  3. 只能操作单个变量:无法原子地操作多个变量

Q3: atomic.Value 有什么限制?

答案

  • 首次 Store 后,后续 Store 的值类型必须相同(否则 panic)
  • Store 不能存 nil(panic)
  • 适合读多写少,不适合高频写入

相关链接