go反对“靠共享内存通信”,主张“通过通信共享内存”;chan以阻塞同步机制实现所有权安全移交,替代mutex手动加锁,但需开发者遵守发送后不修改、接收后独占的约定。
go 并不禁止共享内存,而是明确反对“靠共享内存来通信”——也就是说,别为了传消息而去共用一个变量;但允许、甚至鼓励“为共享内存而通信”,即通过通道把数据所有权安全地移交过去。
为什么 chan 能替代 sync.Mutex 做协调
共享内存模型下,多个 goroutine 直接读写同一块内存,必须靠锁(如 sync.Mutex)控制访问顺序,稍有遗漏就触发 data race。而 chan 的设计天然带同步语义:
- 向未关闭的无缓冲
chan发送会阻塞,直到有 goroutine 在另一端接收 - 从无缓冲
chan接收也会阻塞,直到有 goroutine 发送 - 这种“发送-接收配对”行为,本身就是一次显式的协作点,隐含了时序和所有权转移
它不是消灭了共享,而是把“谁在什么时候能碰这块内存”的规则,从靠人脑记锁、靠代码分散维护,变成由 channel 的阻塞/唤醒机制强制落地。
range over chan 不是遍历完就停,而是等关闭
常见误解:往 pending := make(chan *Resource) 里 send 3 次,for r := range pending 就只跑 3 次。错。
实际行为是:
-
range会一直阻塞等待新值,只要pending没被close() - 哪怕当前为空,只要后续有人 send,循环就继续
- 只有显式调用
close(pending),range才退出
这使得 goroutine 可以长期存活、复用,避免反复启停开销,也支撑了“完成即重投”的闭环逻辑(比如从 complete 收到结果后,再发回 pending 继续处理)。
所有权转移是约定,不是编译器强制
Go 不会在你 send 一个指针后,自动让原 goroutine 失去对该内存的访问权。它只是提供一种清晰的协作模式:
- 发送方把
*Resource通过chan送出,就默认“我不再改它” - 接收方拿到后,默认“现在归我管,我可以读写”
- 如果发送方发完还去改字段,或接收方还在用就释放内存,就会出问题
这种约定依赖团队理解和代码纪律,不是语言兜底。这也是为什么生产代码里常配合 struct 字段注释、静态检查工具(如 go vet -race)一起用。
真正容易被忽略的是:channel 的阻塞语义和生命周期管理,比锁更难调试——因为没有 panic,只有 goroutine 静默卡住。一旦漏关 channel 或忘了谁该关,程序就 hang 在那里,既不报错也不退出。











