Go运行时通过-race检测并发竞态:chan传递指针仅拷贝地址,若收发双方同时操作同一内存,必触发data race;channel提供同步交接语义,但“谁recv谁负责”的所有权契约需靠代码纪律保障,而非语言强制。

这句话不是修辞,而是 Go 运行时对并发行为的约束体现:当你用 chan 传一个指针或结构体时,Go 不会阻止你继续读写它,但一旦多个 goroutine 同时操作同一块内存且没协调,go run -race 几乎必报错。
chan 传递的是值,不是引用魔法
很多人误以为“通过 channel 发送指针 = 自动线程安全”,其实不然。channel 只做拷贝:发送方把指针值(即内存地址)拷贝进缓冲区或直接交给接收方,接收方拿到的是同一个地址的副本。此时若双方都解引用并修改底层数据,就构成竞态。
-
chan *User传递的是地址值,不是锁 - 接收方拿到
*User后,仍可自由读写其字段 - 发送方若在 send 完后还访问该
*User,就和接收方形成共享内存竞争
“共享内存”是结果,不是手段
Go 所谓“通过通信来共享内存”,真实含义是:让某个 goroutine 在某段时间内成为某块内存的唯一拥有者。这个“拥有”靠约定支撑,而非语言强制。
- 发送方发完
u := &User{Name: "A"}到ch后,应停止访问u - 接收方从
ch收到u后,才开始负责它的生命周期 - 这种交接不靠 runtime 检查,靠代码纪律和 code review 保障
为什么不用 mutex 就容易出错?
共享内存模型下,锁的粒度、持有时间、嵌套顺序极易出错;而 channel 天然带同步语义,一次 send/recv 就是一次显式交接点。
- 无缓冲
chan的 send 和 recv 是原子配对,阻塞即等待,天然串行化访问时机 - 缓冲
chan虽可异步,但只要 sender 不在 send 后再碰数据,receiver 就是唯一活跃操作者 - mutex 需要手动加锁/解锁,漏掉一处就全盘崩溃;channel 的“所有权移交”发生在语法层面,更难绕过
真正难的不是写对第一个 goroutine,而是确保所有协程都理解并遵守那条隐含契约:谁 recv 谁负责,recv 前别碰,recv 后别传回。这没有编译器提醒,只有 race detector 和线上 panic 会说话。


















