atomic 原子操作到底有多快?我拿 Mutex 做了个对比测试
前言
写并发代码,锁是免不了的。Mutex 好用,但确实慢。高并发场景,atomic 原子操作是个好东西,但很多人不知道怎么用对。
今天我拿两百万次并发操作做了个测试,结果真的差很远。
一、底层原理
1.1 atomic 到底是怎么工作的
atomic 原子操作利用 CPU 指令级别的支持,确保内存操作的原子性:
graph TD A["多个协程同时写"] --> B["CPU 内存总线"] B --> C["Mutex: 操作系统锁"] B --> D["Atomic: CPU 指令锁"] C --> E["用户态/内核态切换"] D --> F["硬件级别保证"] E --> G["耗时: 微秒级"] F --> H["耗时: 纳秒级"]关键点:
- Mutex 涉及系统调用,昂贵
- atomic 直接调用 CPU 指令
- atomic 不会阻塞协程调度
- 从 GMP 角度看,atomic 不影响 P 的运行队列
1.2 atomic vs Mutex 对比
| 维度 | atomic | Mutex | 差距 |
|---|---|---|---|
| 速度 | 纳秒级 | 微秒级 | 100倍 |
| 协程阻塞 | 不阻塞 | 阻塞 | 显著 |
| 适用场景 | 简单计数器 | 复杂临界区 | - |
| 使用难度 | 低 | 低 | - |
二、快速上手
直接看代码对比:
package main import ( "fmt" "sync" "sync/atomic" "time" ) var ( mutexCount int64 atomicCount int64 mu sync.Mutex ) func testMutex(wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 100000; i++ { mu.Lock() mutexCount++ mu.Unlock() } } func testAtomic(wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 100000; i++ { atomic.AddInt64(&atomicCount, 1) } } func main() { var wg sync.WaitGroup n := 100 start := time.Now() for i := 0; i < n; i++ { wg.Add(1) go testMutex(&wg) } wg.Wait() fmt.Printf("Mutex: %v, count: %d\n", time.Since(start), mutexCount) atomicCount = 0 start = time.Now() for i := 0; i < n; i++ { wg.Add(1) go testAtomic(&wg) } wg.Wait() fmt.Printf("Atomic: %v, count: %d\n", time.Since(start), atomicCount) }在我的机器上跑,atomic 快了至少 10 倍。
三、核心 API / 深水区
3.1 atomic 操作速查
| 函数 | 功能 | 注意事项 |
|---|---|---|
| AddInt64 | 原子加 | 对齐要求 |
| LoadInt64 | 原子读 | 防止乱序 |
| StoreInt64 | 原子写 | 防止乱序 |
| CompareAndSwap | CAS | 乐观锁 |
| Swap | 交换 | 返回旧值 |
3.2 原子操作必须对齐
这个在内存对齐那篇说过,这里再强调一次:
type BadCounter struct { flag byte count int64 // 没对齐! } type GoodCounter struct { count int64 // 对齐了 flag byte }3.3 CAS 乐观锁原理
CAS 是 Compare And Swap,先比较再替换:
var value int64 func update(newValue int64) { for { old := atomic.LoadInt64(&value) if atomic.CompareAndSwapInt64(&value, old, newValue) { return } // 失败就重试 } }四、实战演练
用 atomic 实现高性能计数器:
package main import ( "fmt" "sync" "sync/atomic" "time" ) type AtomicCounter struct { count int64 } func (c *AtomicCounter) Inc() { atomic.AddInt64(&c.count, 1) } func (c *AtomicCounter) Dec() { atomic.AddInt64(&c.count, -1) } func (c *AtomicCounter) Value() int64 { return atomic.LoadInt64(&c.count) } type MutexCounter struct { mu sync.Mutex count int64 } func (c *MutexCounter) Inc() { c.mu.Lock() c.count++ c.mu.Unlock() } func (c *MutexCounter) Dec() { c.mu.Lock() c.count-- c.mu.Unlock() } func (c *MutexCounter) Value() int64 { c.mu.Lock() defer c.mu.Unlock() return c.count } func main() { var wg sync.WaitGroup ac := &AtomicCounter{} mc := &MutexCounter{} start := time.Now() for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 200000; j++ { ac.Inc() } }() } wg.Wait() fmt.Printf("Atomic: %v, value: %d\n", time.Since(start), ac.Value()) start = time.Now() for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 200000; j++ { mc.Inc() } }() } wg.Wait() fmt.Printf("Mutex: %v, value: %d\n", time.Since(start), mc.Value()) }五、避坑指南与最佳实践
💡 **技巧:读也加 Load 不是白加的
不加 Load 可能读到旧值,因为 CPU 乱序执行。
⚠️ **警告:atomic 不能替代 Mutex
复杂的临界区,还得上 Mutex。
✅ **推荐:简单计数器用 atomic
高频场景,atomic 是你的朋友。
六、综合实战演示
生产级并发限流器:
package main import ( "fmt" "sync" "sync/atomic" "time" ) type RateLimiter struct { limit int64 current int64 interval time.Duration lastTime time.Time } func NewRateLimiter(limit int64, interval time.Duration) *RateLimiter { return &RateLimiter{ limit: limit, interval: interval, lastTime: time.Now(), } } func (rl *RateLimiter) Allow() bool { now := time.Now() if now.Sub(rl.lastTime) >= rl.interval { atomic.StoreInt64(&rl.current, 0) rl.lastTime = now } current := atomic.AddInt64(&rl.current, 1) return current <= rl.limit } func main() { rl := NewRateLimiter(100, time.Second) var wg sync.WaitGroup passed := int64(0) blocked := int64(0) for i := 0; i < 200; i++ { wg.Add(1) go func() { defer wg.Done() if rl.Allow() { atomic.AddInt64(&passed, 1) } else { atomic.AddInt64(&blocked, 1) } }() } wg.Wait() fmt.Printf("通过: %d, 阻塞: %d\n", passed, blocked) }七、总结
atomic 和 Mutex 各有各的用法:
- 简单计数用 atomic
- 复杂临界区用 Mutex
- atomic 快但不万能
- 注意内存对齐
选对工具,性能翻倍。