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

Go 中函数传指针只是传地址副本,和传 int 或 string 一样是值传递。真正引发线程安全问题的,是多个 goroutine 同时读写同一块堆内存(比如指向同一个结构体的多个指针),且未加同步控制。
常见错误现象:fatal error: concurrent map writes、数据竞态(race)被 go run -race 检出、结构体字段值“随机”错乱。
const 初始化的全局结构体),或每次调用都新建(&MyStruct{}),通常无风险比如一个计数器结构体被多个 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(),哪怕只改一个字段,也会触发竞态。
int64 对齐写入虽常原子,但 Go 内存模型不保证,且跨平台不可靠sync/atomic 操作基础类型(int32、uint64、指针),比互斥锁更轻量通过 channel 发送指针(如 ch )很常见,但容易忽略:接收方可能在发送方已释放该内存后继续使用它。
典型场景:循环中复用局部变量地址:
for _, v := range data {
go func() {
ch <- &v // ❌ 所有 goroutine 都指向同一个 v 的地址,值不断被覆盖
}()
}正确做法是传值或确保地址唯一:
ch (前提是 v 可拷贝且代价可控)
val := v; ch
sync.Pool 管理临时对象指针,避免 GC 压力与悬垂指针这类 bug 不报 panic,但会导致读到脏数据或崩溃,调试难度高。
当把指针转为 interface{}(如塞进 map[interface{}]interface{} 或 channel chan interface{}),go build -race 可能无法跟踪到底层指针指向的内存是否被并发访问。
interface{} 擦除了类型信息,导致漏报interface{} 作为“通用容器”传递可变状态指针;优先定义具体类型或使用泛型sync.RWMutex 保护)最易被忽略的是:指针安全与否,不取决于它“是不是参数”,而取决于“谁持有它、何时读写、有没有协调”。写并发代码时,盯着内存归属,而不是语法形式。