go中可用channel模拟生成器,需用goroutine发送值并确保defer close(ch),否则range会死锁;推荐返回chan t和error两个值。

Go 里没有 generator 关键字,但可以用 channel 模拟
Go 语言本身不支持 Python 那种 yield 语法,也没有内置 generator 类型。所谓“生成器”,实际是返回 chan T 的函数,靠 goroutine 在后台持续发值、关闭 channel 表示结束。
常见错误是忘记启动 goroutine 或没关 channel,导致调用方死锁或永远收不到 close 信号。
- 必须用
go func() { ... }()启动协程,不能直接同步发送 - 所有路径(包括 panic 分支)都要确保
close(ch),否则 range 会卡住 - 如果生成逻辑可能失败,建议返回
chan T和error两个值,而不是把 error 塞进 channel
func IntsFrom(n int) chan int {
ch := make(chan int)
go func() {
defer close(ch)
for i := n; i < n+3; i++ {
ch <- i
}
}()
return ch
}range channel 时提前退出会导致 goroutine 泄漏
这是最隐蔽也最常踩的坑:调用方用 for v := range ch,但中途 break 或 return,后台 goroutine 还在往已无人接收的 channel 发数据,永久阻塞。
典型场景是“取前 N 个”或“遇到某个值就停”。不能只依赖 range 自动退出。
立即学习“go语言免费学习笔记(深入)”;
- 用带缓冲的 channel(如
make(chan int, 1))能缓解,但不治本 - 正确做法是加一个
donechannel,让生成器监听退出信号 - 或者改用函数式风格:每次调用返回下一个值和是否结束(类似
Next() (T, bool)),避免 channel 生命周期管理
context.Context 是控制生成器生命周期的唯一可靠方式
如果你需要 cancel、timeout、deadline,别自己造 done chan struct{},直接用 context.Context。Go 标准库的惯用法已经验证过它的可靠性。
注意 context.WithCancel 返回的 cancel() 必须被调用,否则 context 不会释放资源;而生成器内部要用 select 同时监听 ctx.Done() 和发送逻辑。
- 不要在生成器里忽略
ctx.Err(),尤其当底层有网络/IO 调用时 - 如果生成器只是纯内存计算,也要响应
ctx.Done(),否则上层无法强制中断 - 避免把
context.Background()硬编码进生成器函数,应由调用方传入
func StringsFromCtx(ctx context.Context, base string) chan string {
ch := make(chan string)
go func() {
defer close(ch)
for i := 0; i < 100; i++ {
select {
case <-ctx.Done():
return
case ch <- fmt.Sprintf("%s-%d", base, i):
}
}
}()
return ch
}不要用 channel 模拟无限流,除非你真需要 lazy + backpressure
很多人一看到“异步数据流”就默认上 channel,但其实多数场景下,[]T 或 func() (T, bool) 更轻量、更可控。
channel 带来额外开销:goroutine 调度、channel 锁、内存分配。如果数据量小、生成快、不需要暂停/恢复,channel 反而是累赘。
- 一次性全量生成 → 用切片,简单直接
- 需要逐个拉取且不想管并发 → 用状态保持型迭代器(闭包 + 可变变量)
- 只有当下游消费慢、上游要等信号再产数(backpressure),才值得上 channel
复杂点在于:channel 的“流语义”是假的——它没有标准的 map/filter/reduce 接口,每个生成器都得自己实现组合逻辑,容易重复造轮子又难测试。










