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

为何偶发sync: negative WaitGroup counter panic?切片并发写入验证异常

切片并发写入导致WaitGroup负计数panic的原因

问题场景

编写了一段验证切片并发写入行为的代码,运行时偶尔触发sync: negative WaitGroup counter的panic(非必现);但将切片容量设置为大于2时,panic现象消失。

相关代码

func main() {
    for i := 0; i < 10000; i++ {
                // 当容量>2时,panic消失
        b := make([]int, 2, 2) 
        b[0], b[1] = 0, 1

        wg := sync.WaitGroup{}
        wg.Add(1)
        go func() {
            b = append(b, 3)
            wg.Done()
        }()
        wg.Add(1)
        go func() {
            b = append(b, 4)
            wg.Done()
        }()
        wg.Wait()
    }
}

错误信息

panic: sync: negative WaitGroup counter

goroutine 12821 [running]:
sync.(*WaitGroup).Add(0x0?, 0xc0000f4ba0?)
        /usr/local/go/src/sync/waitgroup.go:83 +0xda
sync.(*WaitGroup).Done(...)
        /usr/local/go/src/sync/waitgroup.go:108
main.main.func1()
        /Users/liushi/Projects/go/golang-demo/main.go:54 +0x97
created by main.main
        /Users/liushi/Projects/go/golang-demo/main.go:48 +0x11f

Process finished with the exit code 2

原因分析

这个panic的核心原因是栈上的切片扩容操作破坏了相邻WaitGroup的内存结构,具体过程如下:

  1. 当使用make([]int,2,2)创建切片时,由于容量极小,Go编译器会将切片的底层数组分配在main函数的栈上,且与同栈帧的wg变量内存位置相邻。
  2. goroutine执行append(b,3)时,因为切片容量不足触发扩容,需要将栈上的底层数组拷贝到堆上的新数组中。
  3. 极端情况下,这个拷贝操作可能出现内存越界,直接破坏了相邻wg变量的内部结构(比如篡改了WaitGroup的计数器字段)。
  4. 当被破坏的wg执行Done()时,内部计数器会被错误地修改为负数,触发sync: negative WaitGroup counter的panic。
  5. 当切片容量设置为大于2时,Go会将切片底层数组直接分配在堆上,扩容时的拷贝操作不会影响栈上的wg变量,因此不会出现内存破坏的问题,panic也就消失了。

另外补充一点:WaitGroup是值类型,且其方法依赖内部的原子计数器,任何对其内存结构的非法修改都会导致不可预期的错误,这也是这类panic非必现的原因——只有当内存越界恰好命中WaitGroup的关键字段时才会触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 19:17:10