
在 go 中,对只读的 slice 进行并发访问是安全的,无需 mutex 保护;只有当存在写操作或读写混用时才需同步。此外,通过闭包直接捕获 slice 比传指针更符合 go 习惯,也更安全。
在 go 中,对只读的 slice 进行并发访问是安全的,无需 mutex 保护;只有当存在写操作或读写混用时才需同步。此外,通过闭包直接捕获 slice 比传指针更符合 go 习惯,也更安全。
Go 的并发模型建立在清晰的内存模型之上。需要首先明确一个关键术语:[]int{1, 2, 3} 是 slice(切片),而非数组(array)。真正的数组写法是 [3]int{1, 2, 3} 或 [...]int{1, 2, 3}。Slice 是一个轻量级结构体,包含三个字段:指向底层数组的指针、长度(len)和容量(cap)。它本身不持有数据,只是对底层连续内存块的“视图”。
正因为 slice 是只读视图,且其各元素在内存中占据独立地址(如 a[0] 和 a[1] 是不同内存位置),Go 内存模型允许任意数量的 goroutine 同时读取同一 slice 的不同索引——这属于“多读无写”的安全场景,完全不需要互斥锁。官方文档虽未显式列举 slice 元素级并发读取,但依据其对“distinct memory locations”的定义(见 Go Memory Model),这种访问是明确允许的。
因此,原始代码中:
a := []int{1, 2, 3}
var mu = &sync.Mutex{}
for i := 0; i <p>✅ <strong>无需 mutex</strong>:<code>a[0]</code> 被多个 goroutine 只读访问,无竞态风险;<br>
✅ <strong>无需传指针</strong>:Go 闭包可自然捕获外层变量 <code>a</code>,传递 <code>&a</code> 不仅冗余,还可能引发意外修改(如 <code>*a = append(*a, 4)</code>);<br>
⚠️ <strong>但需注意循环变量捕获陷阱</strong>:原代码中 <code>for i 使用了外部变量 <code>i</code>,而 goroutine 内部未绑定当前值,会导致所有 goroutine 共享最终的 <code>i</code> 值(即 10)。应改用 <code>for i := 0; i 并在闭包内显式传参或使用局部副本。</code></code></p><p>更健壮的写法示例:</p><pre class="brush:php;toolbar:false;">a := []int{1, 2, 3}
for i := 0; i <p>? <strong>何时才需要同步?</strong> </p>
- ✅ 多 goroutine 写入同一元素(如
a[0] = x)→ 需锁或原子操作; - ✅ 多 goroutine 写入不同元素但同时调用
append或a = append(a, ...)→ 修改 slice header(指针/len/cap),需锁; - ✅ 混合读写且读写位置重叠 → 必须同步。
? 性能提示:若必须保护整个 slice(如频繁追加),优先考虑 sync.RWMutex(允许多读一写);若仅保护个别索引(如每个 goroutine 固定操作 a[i]),可为每个元素配独立 sync.Mutex 或使用 atomic.Value(适用于小对象)。
总结:Go 的 slice 设计天然支持安全的并发读取。开发者应摒弃“只要共享就加锁”的惯性思维,转而依据实际读写模式精准施加同步——这既是性能优化的关键,也是写出地道 Go 代码的核心素养。










