传指针本身不会引发数据竞争,真正危险的是多个goroutine通过各自持有的 T同时读写同一T实例;只读安全,读写需加锁或原子操作;chan T适合大结构体或需共享修改,chan T适合小结构体且天然线程安全。
会,但不是因为“传了指针”本身,而是因为多个 goroutine 同时读写同一块堆内存且无同步控制。chan *T 只是把指针值发过去,接收方拿到的是该指针的副本——和 func f(p *T) 传参逻辑一致。真正危险的是:多个 goroutine 通过各自持有的 *T 去修改同一个 T 实例。
chan *T 安全*T 字段,其他 goroutine 同时读该字段,必须加 sync.Mutex 或改用原子操作chan T 更稳妥)chan *T 还是 chan T?看场景选哪种取决于数据大小、是否需共享状态、以及是否允许接收方修改原始数据。
chan T:适合小结构体(如 type Point struct{X,Y int}),语义清晰,天然线程安全(复制传递)chan *T:适合大结构体(如含 []byte、map、嵌套指针的结构),避免拷贝开销;或需要接收方能更新原始对象(如任务结果回填)chan []*T 和 chan *[]T 是两回事——后者是指向切片头的指针,极易因底层数组扩容导致意外共享*T 写入关闭 channel 仅表示“不再发新数据”,不影响已发出的指针有效

ch := make(chan *int, 1) x := 42 ch <- &x close(ch) // ✅ 关闭合法 x = 99 // ⚠️ 危险!receiver 可能刚读到 &x 就看到 x=99
const、不可变结构体,或发送前 copy 到只读缓冲区sync.Once 确保初始化只做一次,避免多 goroutine 写同一 *T
atomic.StorePointer 记录地址,receiver 用 atomic.LoadPointer 读取,配合内存屏障当 worker 需反复处理不同数据但初始化开销大(如带缓存的解析器),用 chan *Parser 复用实例比每次 new 更高效。
type Parser struct {
cache map[string]int
buf []byte
}
func worker(ch chan *Parser) {
for p := range ch {
// 复用 p.cache,清空 buf 但不重建 map
p.buf = p.buf[:0]
// ... 解析逻辑
}
}
func main() {
ch := make(chan *Parser, 1)
go worker(ch)
p := &Parser{cache: make(map[string]int)}
ch <- p // 传指针,避免重复 alloc cache
close(ch)
}
这里关键是:worker 不修改 p.cache 的指针值,只读写其内容;且没有其他 goroutine 同时往 p.cache 写——否则仍需锁。
最易被忽略的一点:指针传递不等于自动线程安全,安全边界永远由你对内存的访问方式决定,而不是 channel 类型声明。