You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang中用CAS实现无锁切片Append失败,求问题原因与正确方案

无锁并发切片Append的问题分析与修复

为什么使用append的版本会出现并发安全问题

Go语言中切片的append操作不是原子性的,且存在底层数组复用的特性:

  • 当原切片的底层数组还有剩余容量(cap)时,append会直接在原数组末尾添加元素,不会分配新数组。这意味着多个goroutine同时对同一个切片执行append时,会直接修改共享的底层数组,导致元素被覆盖、数据错乱。
  • 即使append触发了新数组分配,在你调用CompareAndSwap之前,其他goroutine可能已经修改了sync.Map中存储的切片指针,导致你基于旧切片生成的freshActions已经过时,最终CAS失败或者覆盖正确数据。

在你的错误实现中,freshActions := append(*oldActions, action)生成的新切片可能和*oldActions共享底层数组,当多个goroutine同时执行这一步时,会互相干扰,导致最终存储的切片元素缺失或重复,进而执行时sum计算错误。

正确的CAS实现无锁切片Append

要实现安全的无锁切片Append,必须保证每次更新都基于独立的底层数组,避免共享修改。核心思路是:

  1. 加载当前切片指针
  2. 创建一个全新的切片,长度为原切片长度+1,手动复制原切片的所有元素
  3. 添加新元素到新切片
  4. 使用CompareAndSwap原子替换旧切片指针,失败则循环重试

正确实现代码如下:

func (group *Actions) Add(key string, action func()) {
    for {
        value, loaded := group.store.Load(key)
        if !loaded {
            actions := []func(){action}
            value, loaded = group.store.LoadOrStore(key, &actions)
            if !loaded {
                return
            }
        }

        oldActions := value.(*[]func())
        // 创建新切片,独立底层数组
        freshActions := make([]func(), len(*oldActions)+1)
        copy(freshActions, *oldActions)
        freshActions[len(*oldActions)] = action

        if group.store.CompareAndSwap(key, oldActions, &freshActions) {
            return
        }
    }
}

关键改进点

  • 使用make创建固定长度的新切片,确保拥有独立的底层数组,彻底避免并发修改共享数组的问题
  • 通过copy复制原切片元素,保证新切片的初始状态是原切片的完整副本
  • 循环重试CompareAndSwap,直到原子替换成功,确保最终存储的切片是最新状态

这样修改后,sync.Map中存储的每个切片指针都对应独立的底层数组,CAS操作能保证每次更新都是原子性的,不会出现竞态条件,最终执行Exec时能得到正确的count和sum结果。

内容的提问来源于stack exchange,提问作者ksCaesar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:04:52