指针参数本身不破坏线程安全,真正引发问题的是多个goroutine并发读写同一堆内存且无同步;只读或每次新建指针则安全,共享可变状态必须加锁或原子操作。

指针参数本身不破坏线程安全,但共享可变状态会
Go 中函数传指针只是传地址副本,和传 int 或 string 一样是值传递。真正引发线程安全问题的,是多个 goroutine 同时读写同一块堆内存(比如指向同一个结构体的多个指针),且未加同步控制。
常见错误现象:fatal error: concurrent map writes、数据竞态(race)被 go run -race 检出、结构体字段值“随机”错乱。
- 如果指针指向的是只读数据(如
const初始化的全局结构体),或每次调用都新建(&MyStruct{}),通常无风险 - 若多个 goroutine 共享一个指针变量(如闭包捕获、全局变量、channel 传递后复用),且对所指对象做写操作,就必须加锁或改用原子操作
- 切片、map、channel 本身是引用类型,即使不传指针,它们的底层数据也可能被并发修改——这点常被误认为“没传指针就安全”
goroutine 间通过指针共享结构体时,必须显式同步
比如一个计数器结构体被多个 goroutine 通过指针访问:
type Counter struct {
mu sync.Mutex
n int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.n++
}这里 *Counter 是参数类型,但关键不是“用了指针”,而是 c.mu 提供了临界区保护。漏掉 Lock()/Unlock(),哪怕只改一个字段,也会触发竞态。
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖“小字段写入是原子的”:x86 上
int64对齐写入虽常原子,但 Go 内存模型不保证,且跨平台不可靠 - 避免在锁内做耗时操作(如 HTTP 调用、大循环),否则阻塞其他 goroutine
- 优先用
sync/atomic操作基础类型(int32、uint64、指针),比互斥锁更轻量
channel 传递指针需格外小心生命周期
通过 channel 发送指针(如 ch )很常见,但容易忽略:接收方可能在发送方已释放该内存后继续使用它。
典型场景:循环中复用局部变量地址:
for _, v := range data {
go func() {
ch <- &v // ❌ 所有 goroutine 都指向同一个 v 的地址,值不断被覆盖
}()
}正确做法是传值或确保地址唯一:
- 改用值传递:
ch (前提是v可拷贝且代价可控) - 在循环内取地址:
val := v; ch - 用
sync.Pool管理临时对象指针,避免 GC 压力与悬垂指针
这类 bug 不报 panic,但会导致读到脏数据或崩溃,调试难度高。
interface{} 包装指针时,竞态检测可能失效
当把指针转为 interface{}(如塞进 map[interface{}]interface{} 或 channel chan interface{}),go build -race 可能无法跟踪到底层指针指向的内存是否被并发访问。
- 竞态检测器依赖静态分析指针来源,而
interface{}擦除了类型信息,导致漏报 - 避免用
interface{}作为“通用容器”传递可变状态指针;优先定义具体类型或使用泛型 - 若必须用,确保所有对该指针的访问路径都在同一同步机制下(如统一由某个
sync.RWMutex保护)
最易被忽略的是:指针安全与否,不取决于它“是不是参数”,而取决于“谁持有它、何时读写、有没有协调”。写并发代码时,盯着内存归属,而不是语法形式。










