atomic.LoadInt64更安全,因其保证原子读取并隐含acquire内存屏障,防止指令重排和缓存不一致,确保后续读取看到最新值;直接读变量无此保障,会触发data race。
为什么 atomic.LoadInt64 比直接读变量更安全
直接读写共享变量(如 int64)在多核 cpu 上可能因指令重排或缓存不一致,导致 goroutine 看到过期值。比如一个写 goroutine 执行了 data = "ready"; atomic.storeint32(&flag, 1),另一个 goroutine 若用普通读取 flag 再读 data,可能看到 flag == 1 但 data 仍是空字符串——因为编译器或 cpu 把这两条读指令重排了,或者没从最新缓存同步。
atomic.LoadInt64 不仅保证读操作原子,还隐含 acquire 语义:它之后的所有内存访问(包括非原子读)不会被重排到它前面,且会强制从主内存或一致性缓存中加载最新值。这是靠底层插入内存屏障实现的,你不用手写 runtime.GC() 或 sync/atomic 以外的同步原语。
- 别用
int64变量裸读裸写,哪怕它在 64 位机器上“看起来”是原子的——Go 规范不保证,且go build -race会报 data race - 所有跨 goroutine 共享的整型状态(如开关、计数器、版本号),统一用
atomic.Load*/Store*访问 -
atomic.LoadUintptr和atomic.LoadPointer是指针级可见性的关键,尤其在无锁数据结构中
sync.RWMutex 写锁释放时的内存屏障不可跳过
当你调用 mu.RUnlock(),它不只是解锁;它会在底层插入 full memory barrier,确保此前所有写操作对后续获取该锁的 goroutine 完全可见。这个屏障是 sync.RWMutex 正确性的基石,不能靠 “我只读不写” 就省略。
常见错误是误以为读锁(RUnlock)不需要屏障——其实读锁获取(RLock)本身不插屏障,但写锁释放(Unlock)一定插。这意味着:如果某 goroutine 在写锁内更新了结构体字段并 Unlock(),其他 goroutine 接着用 RLock() 读,就能看到完整更新;但如果中间夹了非原子读或普通赋值,屏障效果就断了。
- 不要在
RWMutex保护的临界区内混用原子操作和普通变量赋值——要么全原子,要么全锁保护 - 写锁路径比读锁重得多,不仅因为互斥,更因每次
Unlock()都触发内存屏障 + 唤醒等待队列,高写频场景下应优先考虑分片或sync.Map(仅当读 >90%) -
RLock()/RUnlock()之间禁止写共享数据,否则即使加了读锁,也会破坏屏障链,引发脏读
sync.Map 的 LoadOrStore 为什么不是“懒求值”
LoadOrStore 的第二个参数是 interface{} 类型,Go 会在调用前立即求值——不管 key 是否已存在。这和你直觉里的“查不到才构造”完全不同。例如 m.LoadOrStore("cfg", loadFromDB()),每次调用都会执行 loadFromDB(),哪怕 key 已在 read map 中。
这是因为 LoadOrStore 是单次原子操作,无法拆成“先查后算”,它的设计目标是避免锁 + 减少竞争,而不是控制副作用时机。真正的懒加载必须手动判断:
if val, loaded := m.Load(key); loaded {
return val
}
val := heavyCalc() // ✅ 只在这里执行
m.Store(key, val)
return val
-
LoadOrStore返回的loadedbool 只表示 key 是否已存在,不改变传入 value 的求值时机 - 传入函数调用结果(如
time.Now().String())会导致时间戳永远是调用时刻,而非存入时刻 - 高频写场景下,
LoadOrStore还会触发dirtymap 提升,带来一次性扩容开销,比直接Store更重
atomic.CompareAndSwapPointer 在无锁链表中的实际约束
atomic.CompareAndSwapPointer 是构建无锁结构(如并发安全栈、队列)的基础,但它只保证指针比较交换的原子性,不保证所指向内存内容的可见性。比如你用 CAS 更新一个节点的 next 字段,新节点的 value 字段若未用原子操作或屏障写入,其他 goroutine 可能读到零值或部分初始化状态。
正确做法是:新节点所有字段必须在 CAS 前完成初始化,并用 atomic.StorePointer 或带 release 语义的写入(如 sync/atomic 的 Store*)发布;CAS 成功后,后续读需配合 atomic.LoadPointer(带 acquire 语义)才能看到完整内容。
- CAS 失败不等于数据错——它只说明当前值不匹配,可能是别人抢先更新了,要重试
- 无锁 ≠ 无内存屏障:每个 CAS 前后都隐含内存序约束,滥用
unsafe.Pointer绕过 atomic 会导致屏障失效 - 除非你真在写 runtime 或高性能中间件,否则优先用
sync.Mutex或 channel,CAS 容易写出 ABA 问题或无限重试
Store 后跟 Load,或 CompareAndSwap 前后对同一块内存的访问顺序。漏掉一个 acquire/release,就可能让整个无锁逻辑在多核上间歇性失效。


















