值类型变量本身不能被共享,必须转为指针;Go中所有传递都是值传递,仅当值为指针时才实现内存共享,切片/map/channel是带header的值类型,sync.Pool不用于跨goroutine共享,channel传指针可安全转移所有权,逃逸分析决定指针是否真正指向堆内存。

值类型变量本身不能被共享,必须转为指针
Go 中的 int、string、struct 等值类型在赋值或传参时会复制整个数据。所谓“共享”,本质是让多个变量指向同一块内存地址——这只能通过 *T(指向该类型的指针)实现。
常见误解是试图对值类型做“引用传递”,但 Go 没有引用类型(reference type)这一概念;所有传递都是值传递,只是当值是 *T 时,复制的是指针本身(8 字节地址),而非它指向的内容。
- 直接传
struct{}:每次调用都拷贝全部字段,无法反映其他 goroutine 的修改 - 传
*struct{}:多个 goroutine 操作同一内存,需配合sync.Mutex或原子操作防竞争 - 切片(
[]byte)、map、channel 是引用语义的封装,但底层仍依赖指针;它们本身是值类型(含 header 字段),复制时只复制 header,不是底层数组
使用 sync.Pool 避免高频分配,但不用于跨 goroutine 共享
sync.Pool 是复用临时对象的机制,常被误认为“共享池”。它不保证对象被谁获取,也不提供同步访问能力,因此不能替代指针 + 锁的共享方案。
典型误用:pool.Get() 返回的对象可能已被其他 goroutine 修改过,且无任何保护。若真需要共享,应从池中取出后显式初始化,或仅用于无状态中间对象(如 bytes.Buffer)。
立即学习“go语言免费学习笔记(深入)”;
-
sync.Pool适合:频繁创建销毁的临时缓冲区、解析器上下文等 - 不适合:持有业务状态、需多 goroutine 协同读写的结构体实例
- 池中对象可能被任意时刻回收,不可依赖其生命周期
通过 channel 传递指针实现安全共享
直接暴露全局指针变量容易引发竞态,更推荐用 channel 作为“所有权转移”通道。把 *T 发送到 channel,接收方获得唯一访问权,避免同时读写。
type Counter struct {
val int
}
ch := make(chan *Counter, 1)
ch <- &Counter{val: 0} // 发送指针
go func() {
c := <-ch // 接收方获得独占访问
c.val++
ch <- c // 归还(可选)
}()
- channel 容量设为 1 可天然限制同时最多一个 goroutine 持有该指针
- 适用于状态机、资源代理等场景,比锁更清晰表达“谁在负责”
- 注意:不要在发送后继续使用原指针,否则造成数据竞争
逃逸分析决定值是否真的在堆上分配
即使你写了 &x,编译器也可能优化掉指针(如局部小 struct 未逃逸),导致你以为共享了,实际仍是副本。用 go build -gcflags="-m" 查看逃逸信息:
$ go build -gcflags="-m" main.go main.go:12:2: &s escapes to heap
- 若提示
escapes to heap,说明该值确实分配在堆上,指针可安全跨栈帧/ goroutine 使用 - 若提示
does not escape,则&s可能被优化为栈地址,返回给其他 goroutine 将导致未定义行为 - 强制逃逸的方法包括:赋值给全局变量、传入 interface{}、作为 channel 元素发送
真正共享的前提,是值必须驻留在堆上且生命周期足够长;否则,哪怕用了指针,也可能是悬垂指针。










