<p>单向channel是Go类型系统中带运行时语义的关键特性:编译期强制方向检查,<-chan int只能接收,chan<- int不能close,漏关会导致goroutine泄漏。</p>

传入 chan int 却想只读?编译直接报错
Go 编译器会严格检查 channel 的方向性。如果你函数签名写的是 func f(c chan int),调用方传 chan<- int 或 <-chan int 都会失败——双向 channel 和单向 channel 类型不兼容,哪怕底层结构一样。
真正能接受只读 channel 的参数类型必须显式声明为 <-chan int;同理,只写必须用 chan<- int。这是类型系统强制的契约,不是可选提示。
- 错误写法:
func process(c chan int) { <-c }→ 无法接收<-chan int实参 - 正确写法:
func process(c <-chan int) { <-c }→ 只读安全,且能接收chan int和<-chan int - 注意:
chan int可以隐式转换为<-chan int或chan<- int,但反过来不行
chan<- int 参数不能关闭?运行时报 panic
函数如果只声明了 chan<- int 参数,说明它只负责发送,不持有接收端权限。此时若在函数内调用 close(c),Go 编译器不会拦(因为语法合法),但运行时只要该 channel 真实是单向的(比如由 make(chan int) 创建后转成 chan<- int 传入),close 就会 panic:"panic: close of send-only channel"。
- 根本原因:单向 channel 的方向信息在运行时仍被保留,
close检查实际类型,不看变量声明 - 安全做法:只对双向 channel 调用
close;若必须由发送方关闭,请用chan int类型传参,并确保调用方清楚责任 - 常见误操作:把
make(chan int, 10)转成chan<- int后传给一个“以为自己能关”的函数
为什么用 <-chan string 而不是 interface{} 做管道输入?
用 interface{} 接收 channel 看似灵活,实则放弃所有类型和方向检查,等于把问题推给运行时或调用者。而 <-chan string 明确表达“我只从这个管道读 string”,编译器能帮你挡住:chan int、chan<- string、<-chan int 这三类错误。
立即学习“go语言免费学习笔记(深入)”;
- 性能无差异:单向 channel 是编译期类型约束,无运行时代价
- 可读性提升:看到参数类型就知道数据流向和内容类型,不用翻文档或猜逻辑
- 配合 select 更安全:在
case <-c:中,c是<-chan T才能编译通过,避免误写成case c <- x:
goroutine 泄漏常藏在 <-chan bool 参数里
很多工具函数用 <-chan bool 做 done 信号,比如 func watch(done <-chan bool) { for { select { case <-done: return } } }。但如果调用方传入的是未关闭的 chan bool(或忘了 close),这个 goroutine 就永远卡在 select 上,无法退出。
- 关键点:单向 channel 不改变底层 channel 的生命周期,
<-chan bool仍需被关闭才能让接收方退出 - 建议:函数内部加超时兜底,或文档明确要求调用方保证
done必关;不要假设“传进来的 done 总是有效的” - 调试线索:pprof 查 goroutine 数量持续上涨,堆栈停在
runtime.gopark+ 你的 channel 读操作
事情说清了就结束。单向 channel 不是语法糖,它是 Go 类型系统里少数几个带运行时语义的类型——方向错了,编译过不了;关错了,运行就崩;漏关了,goroutine 就飘着。










