直接传指针进goroutine易出错,因循环变量复用导致所有goroutine共享同一地址;sync.Mutex不保护指针本身而只保护临界区内的解引用操作;unsafe.Pointer绕过GC跟踪易致悬垂指针;channel传指针需明确所有权与生命周期。
为什么直接传指针进 goroutine 容易出错
因为 goroutine 启动是异步的,如果循环中用
取地址并启动 goroutine,所有 goroutine 最终可能都指向同一个内存地址(即循环变量
的最后一轮值)。
典型现象:
输出全是最后一个
根本原因:Go 中
复用循环变量,
总是取同一块栈地址
修复方式:在循环体内显式拷贝变量,比如
,或用局部变量
注意:即使传的是结构体指针,只要它指向的是循环内反复赋值的变量,问题依旧存在
sync.Mutex 保护指针所指向的数据,不是保护指针本身
指针变量本身是可复制的,
不能靠“锁住指针”来保证并发安全;必须确保所有对指针所指向内存的读写,都在临界区内完成。
错误写法:
—— 这只锁了赋值动作,后续通过
读写仍不安全
正确做法:把所有解引用操作(
)包进
区间
常见陷阱:把 mutex 放在结构体里但没导出,外部无法调用
;或误以为 “指针+mutex 字段” 就自动线程安全
性能提示:避免在 hot path 中频繁加锁解引用;考虑用
操作简单类型(如
),但仅限原子读写,不支持复合操作
unsafe.Pointer 转换指针类型时丢失类型安全与 GC 可见性
在并发场景下混用
和普通指针,容易导致 GC 误判对象存活状态,引发悬垂指针或提前回收。
典型错误:
—— 若
是局部变量且函数已返回,
指向的内存可能被回收
GC 不跟踪
,也不会将其视为根对象;一旦没有其他 Go 指针指向该内存,就可能被清理
并发下更危险:一个 goroutine 正在用
访存,另一个触发 GC,且无强引用维持对象生命周期
替代方案:优先用
管理临时对象;必须用
时,确保原始 Go 指针在整个使用周期内持续存活(例如全局变量、堆分配对象)
channel 传递指针需明确所有权和生命周期边界
通过 channel 发送指针,本质是传递内存地址,接收方获得的是同一份数据的访问权。谁负责释放?是否允许并发读写?必须由设计者约定清楚。
立即学习
“
go语言免费学习笔记(深入)
”;
常见误用:发送指向栈变量的指针(如
),接收方收到后
已失效
安全前提:指针必须指向堆分配对象(如
或
),且生命周期长于 channel 传输和消费过程
并发风险:多个 goroutine 从 channel 收到同一指针后同时修改,等价于裸指针共享,仍需额外
同步机制
建议:除非有明确性能收益(如大结构体),否则优先传值;若必须传指针,配合
或文档注明“该指针仅由接收方独占使用”
指针本身不并发安全,安全与否取决于你怎么用它——尤其是它指向哪儿、谁在什么时候访问、有没有其他 goroutine 在同一时间做别的事。最容易被忽略的,是那些看起来“只是读一下”的解引用操作,在并发路径里也可能成为竞态源头。
&vvfor _, v := range items { go func() { fmt.Println(v) }() }vrange&vgo func(val int) { fmt.Println(val) }(v)val := v; go func() { ... }(val)sync.Mutexvar p *int; mu.Lock(); p = &x; mu.Unlock()p*pmu.Lock()/Unlock()Lock()sync/atomic*int32unsafe.Pointerp := &x; up := unsafe.Pointer(p); ... go func() { *(*int)(up) = 42 }()xupunsafe.Pointerunsafesync.Poolunsafefunc() { x := 42; ch <- &x }xnew(T)&struct{}sync.Once