直接对切片并发 append 会出错,因 append 非原子操作,扩容时多 goroutine 可能覆盖指针或漏拷贝,导致数据丢失或 panic;-race 通常报 data race on slice header。

为什么直接对切片做并发 append 会出错
因为 append 不是原子操作:当底层数组容量不足时,它要分配新数组、复制旧数据、更新指针和长度。多个 goroutine 同时触发扩容,可能彼此覆盖指针或漏拷贝元素,最终导致数据丢失或 panic —— go run -race 通常会报出 data race on slice header 或类似提示。
sync.RWMutex 适合读多写少的切片场景
如果你的切片主要被大量 goroutine 并发读取,但写入频率很低(比如配置列表、白名单缓存),sync.RWMutex 比 sync.Mutex 更合适:
- 读操作用
RWMutex.RLock()/RWMutex.RUnlock(),允许多个 goroutine 同时读 - 写操作仍必须用
RWMutex.Lock()/RWMutex.Unlock(),此时所有读写都会阻塞 - 注意:没配对调用
RUnlock()会导致 goroutine 泄漏,且-race很难检测到 - 如果读操作本身很轻(比如只取
len(s)或单个索引),RWMutex反而比Mutex开销更大
写密集或结构固定时,优先考虑 channel + 预分配
当你要批量收集结果(如并发请求后汇总响应),别急着加锁——用 channel 把「所有权」传递出去更自然、更安全:
- 启动 N 个 goroutine,每个向自己的局部切片写入,完成后把切片发到 channel
- 主 goroutine 从 channel 收集所有切片,用
append(dst, src...)合并 - 若总长度已知,提前用
make([]T, 0, total)预分配目标切片,避免多次扩容 - 这种模式下,零共享、零锁、零竞态,且合并阶段可控制顺序或去重
别忽略切片底层数组共享带来的隐式竞争
即使没用 append,只要多个切片共用同一底层数组,修改一个就会影响另一个:
-
s1 := arr[1:3]和s2 := arr[2:4]共享arr,改s1[1]就等于改s2[0] - 这种竞争不会被
-race检测到,因为没涉及指针或变量本身的读写,只发生在底层数组内存上 - 若需隔离,用
copy(newSlice, oldSlice)显式复制,或从make新建独立底层数组
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











