值类型变量本身不能被共享,必须转为指针;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 是复用临时对象的机制,常被误认为“共享池”。它不保证对象被谁获取,也不提供同步访问能力,因此不能替代指针 + 锁的共享方案。
典型误用:pool.Get() 返回的对象可能已被其他 goroutine 修改过,且无任何保护。若真需要共享,应从池中取出后显式初始化,或仅用于无状态中间对象(如 bytes.Buffer)。
sync.Pool 适合:频繁创建销毁的临时缓冲区、解析器上下文等直接暴露全局指针变量容易引发竞态,更推荐用 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 // 归还(可选)
}()
即使你写了 &x,编译器也可能优化掉指针(如局部小 struct 未逃逸),导致你以为共享了,实际仍是副本。用 go build -gcflags="- 查看逃逸信息:
$ go build -gcflags="-m" main.go main.go:12:2: &s escapes to heap
escapes to heap,说明该值确实分配在堆上,指针可安全跨栈帧/ goroutine 使用does not escape,则 &s 可能被优化为栈地址,返回给其他 goroutine 将导致未定义行为真正共享的前提,是值必须驻留在堆上且生命周期足够长;否则,哪怕用了指针,也可能是悬垂指针。