Go中指针本身不影响并发安全,真正决定安全的是如何用指针访问和修改所指向的数据;多个goroutine调用同一* T方法出问题是因为共享内存未同步,需用锁或原子操作保护。

Go 中的指针本身**完全不影响并发安全**——它只是个地址值,和 int 或 string 一样,复制、传递、存储都无副作用。真正决定并发是否安全的,是你**怎么用这个指针去访问和修改背后的数据**。
为什么多个 goroutine 调用同一个 *T 的方法会出问题?
因为方法接收者 *T 是个指针,调用时所有 goroutine 实际上都在操作同一块内存(比如 t.value)。如果方法里直接读写结构体字段,又没加锁或原子操作,go run -race 一定能抓到数据竞争。
- 错误示例:未加锁的自增
type Counter struct {
value int
}
func (c *Counter) Inc() { c.value++ } // ❌ 并发调用会丢计数
- 正确做法:用
sync.Mutex或sync/atomic
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
注意:mu 必须是值类型(不能是 *sync.Mutex),否则结构体复制后锁就失效了。
什么时候传 *T 反而更安全?
当你要**避免共享**时。比如用 channel 传递 *Task,让单个 goroutine 拥有该指针的“所有权”,其他 goroutine 不再访问它——这比共享一个 *T 加锁更干净。
立即学习“go语言免费学习笔记(深入)”;
- 安全场景:消息传递,非状态共享
ch := make(chan *Task, 10)
go func() {
for t := range ch {
process(t) // ✅ 此 goroutine 独占 t,无需锁
}
}()
这时指针只是高效传递大对象的载体,不构成并发风险;风险来自“多处同时读写同一内存”,而不是“用了指针”。
常见踩坑:你以为加了锁,其实锁错了地方
典型误操作:对 map、slice、channel 等引用类型字段单独加锁,却忘了它们底层仍指向共享内存;或者把锁放在指针字段上(如 mu *sync.Mutex),导致每次结构体拷贝都带一个新锁。
- 错误:map 未被锁保护
type Config struct {
data map[string]string // ❌ map 本身非并发安全
}
func (c *Config) Set(k, v string) {
c.data[k] = v // 即使 c 是 *Config,这里也竞态!
}
- 正确:用
sync.RWMutex包裹整个 map 访问
type Config struct {
mu sync.RWMutex
data map[string]string
}
func (c *Config) Get(k string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[k]
}
记住:锁保护的是**数据访问行为**,不是指针本身;map、slice、chan 都需要外部同步,Go 不会自动帮你线程安全化。
最易被忽略的一点:**nil 指针检查和竞态检测不是一回事**。你可能加了 if p != nil 防 panic,但若多个 goroutine 同时在读/写 p.field,依然会触发 -race 报告——判空只防崩溃,不防竞争。










