<p>传值即可,但必须按角色选单向类型(chan<- 或 <-chan),以确保发送与接收职责分离,提升类型安全与代码可读性。</p>

Go 函数间用 chan 传数据,核心就一条:**传值即可,但必须按角色选单向类型(chan 或 <code>),否则并发安全和可维护性立刻崩塌。**
函数参数该传 chan int 还是 ?
取决于函数干啥事——不是“能不能”,而是“该不该”有写权限。
- 只消费数据(如日志处理器、校验器):用
。编译器会拦住你往里写,避免意外发送或重复关闭 - 只生产数据(如数据生成器、API轮询器):用
chan。调用方传进来的 channel 必须支持写,且你不能读它 - 既读又写(极少见,比如带反馈的协程控制器):才用
chan int;但多数时候说明设计已偏离 pipeline 思路 -
chan int可隐式转成单向类型,但反过来不行——所以对外暴露接口时,优先声明单向类型
为什么传 *chan int 是错的?
因为 chan 本身是引用语义的值类型,底层是结构体指针。传 *chan 不仅多余,还埋雷:
- 调用方无法感知你是否在函数内执行了
*c = make(chan int)—— 管道被悄悄替换了 - 多个 goroutine 共享一个
*chan指针,可能误关别人还在用的 channel - 关闭逻辑混乱:
close(*c)和close(c)行为一样,但语义完全错位 - Go 官方文档和所有标准库都只传
chan值,没例外
管道在函数链中怎么不卡死?
常见卡死不是因为逻辑错,而是没处理好 channel 生命周期和阻塞边界:
立即学习“go语言免费学习笔记(深入)”;
- 无缓冲 channel:发送和接收必须同时就绪,否则永久阻塞。函数链中若某环节没启动 goroutine 接收,上游就卡住
- nil channel:传入
nil的到 <code>for range或select,直接 hang 住——必须在函数入口判空,或用default分支兜底 - 关闭时机错:消费者函数不能关 channel;只有唯一生产者 goroutine 才能关,且必须在所有发送完成后
- 接收端没退出:用
for v := range ch时,如果 sender 忘关 channel,循环永不结束
实际流转示例:producer → transformer → consumer
这不是玩具代码,是真实可复用的数据流骨架:
func producer(out chan<- int) {
defer close(out)
for i := 0; i < 5; i++ {
out <- i * 2
}
}
<p>func transformer(in <-chan int, out chan<- int) {
for v := range in {
out <- v + 1
}
close(out)
}</p><p>func consumer(in <-chan int) {
for v := range in {
fmt.Println("got:", v)
}
}</p><p>// 启动
ch1 := make(chan int)
ch2 := make(chan int)
go producer(ch1)
go transformer(ch1, ch2)
go consumer(ch2)
注意每个环节的 channel 类型标注、close 位置、以及 go 启动——漏掉任何一个,整个链就断在某个环节上。最易忽略的是:**transformer 关闭 out 是安全的,因为它独占写权限;但它绝不能关 in,哪怕 in 是 chan int 类型。**

















