goroutine 那个"开销比 pthread 小到可忽略",得先把"小在哪"和"小到什么程度"拆开看。pthread 默认栈在 glibc 下常常 2MB 起(有些发行版能到 8MB),Go 的 goroutine 初始栈只有 2KB,按需增长到 1GB 上限,量级上确实把线程按在地上摩擦。可"可忽略"在百万级并发时就不太经得住了——每个 goroutine 至少背着栈加 runtime 的 g 结构体,粗算 2KB×10^6 ≈ 2GB 内存,这账还是得算的,不能说完全没有成本。
channel 那块我倒想补一句。Rob Pike 那句"不要通过共享内存来通信,而要通过通信来共享内存"很漂亮,但 channel 不是 mutex 的升级替代,它俩是两种取舍。Go 的 channel 底层 hchan 结构里照样有锁,单次收发比直接读写一个共享变量慢一截。简单的并发计数器,atomic 或 mutex 通常比 channel 快好几倍。真正适合 channel 的,恰恰是你后半段说的那种局面——传递所有权、在同步点汇合——"各发各的消息然后汇合"正是它设计来对付的场景,这部分你用得很准。
不过死锁这事,channel 只是换了个形态还在。无缓冲 channel 没接收方就一直阻塞,select 忘了写 default 也容易整个卡住。它把"谁先动谁等"的时序问题从锁竞争挪到了消息拓扑上,确实清爽,但前提是得先把消息流画清楚,不然只是把坑从明处挪到暗处。
你最后那条静态 binary 确实是真香。顺手提一句:开了 CGO 默认是动态链接 libc 的,纯静态得显式 CGO_ENABLED=0,或者 -extldflags ‘-static’,否则丢到 alpine 那种 musl 环境里照样起不来,我是被这个坑过一回才老老实实记下来的。