
go 中只读访问共享切片无需互斥锁;多个 goroutine 同时读取不同元素是内存安全的,仅当存在写操作或读写竞争时才需同步保护。
go 中只读访问共享切片无需互斥锁;多个 goroutine 同时读取不同元素是内存安全的,仅当存在写操作或读写竞争时才需同步保护。
在 Go 并发编程中,理解切片(slice) 与数组(array) 的本质区别是避免误用同步机制的关键。题中代码 []int{1,2,3} 创建的是一个切片——它是一个包含底层数组指针、长度(len)和容量(cap)的轻量级结构体,而非固定大小的数组(如 [3]int{1,2,3})。切片本身是值类型,但其底层数据通常分配在堆上,且各元素在内存中占据独立地址。
✅ 只读场景:无需加锁
根据 Go 内存模型,只要多个 goroutine 仅对同一内存区域进行只读访问,无论是否并发,都是完全安全的。由于 a[0]、a[1]、a[2] 指向底层数组中不同的内存位置,即使 10 个 goroutine 同时循环读取 a[0](如示例所示),也不存在数据竞争(data race)。因此,原代码中的 mu.Lock()/mu.Unlock() 不仅多余,还会引入不必要的性能开销和锁争用。
❌ 错误实践分析
原代码还存在两个典型问题:
-
变量捕获陷阱:外层
for i 使用了未声明的 <code>i(应为for i := 0; i ),且 goroutine 内部闭包捕获的是外部 <code>i的地址(因使用&a传参),但实际并未修改a,纯属冗余; -
指针传递无必要:切片本身已包含指向底层数组的指针,直接传值即可高效共享数据,
&a反而增加复杂度且易引发误解。
? 推荐写法(简洁、安全、符合 Go 习惯)
package main
import (
"fmt"
"sync"
"time"
)
func main() {
a := []int{1, 2, 3}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for _, v := range a {
// 安全:只读访问,无需锁
fmt.Printf("goroutine %d reads a[0] = %d\n", i, a[0])
// 注意:此处 i 是闭包捕获的循环变量,实际会输出 10 个 '10' —— 需修正
}
}()
}
wg.Wait()
}⚠️ 重要注意事项
- 若需在 goroutine 中修改切片内容(如
a[0] = 99),且多个 goroutine 可能写入同一索引,则必须对对应位置加锁(可考虑sync.RWMutex或按索引分片加锁); - 若需追加元素(
a = append(a, x)),因可能触发底层数组扩容并更改指针,属于写操作,必须整体同步; - 闭包中直接使用循环变量
i会导致所有 goroutine 共享最终值(经典“循环变量陷阱”),应改为go func(idx int) { ... }(i)显式传参。
? 总结
Go 的并发安全哲学是:“共享内存通过通信,而非通信通过共享内存”。对只读切片,信任语言内存模型,避免过早加锁;对读写混合场景,优先通过 channel 协调,或精准锁定最小临界区(如单个元素),而非粗粒度锁整个切片。

















