go中无原生generator,但可用chan+goroutine实现等效功能:懒求值、协作式控制流、边界解耦;需用goroutine启动生产逻辑,消费端range遍历时须显式close()。

Go 里没有原生 generator,chan 是最贴近的替代方案
Go 不支持 Python 那种 yield 语法,也没有语言级 generator。但用带缓冲或无缓冲的 chan + goroutine 就能实现等效行为:按需产生无限序列、异步生成、消费可控。
关键不是“模拟 generator”,而是理解它的本质:**懒求值 + 协作式控制流 + 边界解耦**。Go 的 chan 天然承担了数据通道和同步点双重角色。
- 必须用 goroutine 启动生产逻辑,否则
chan写入会阻塞主流程 - 读端用
range遍历chan最自然,但记得在生成端显式close(),否则range永不退出(除非用for { select { case x := ) - 若要“可重置”或“多消费者”,不能复用同一
chan—— 每次调用应返回新chan,否则状态污染
func Fibonacci() chan int 这类函数容易犯的三个错
这是最常被抄的示例,但线上出问题往往就栽在这儿:
- 没加
defer close(ch):导致消费者永远卡在range,尤其配合context.WithTimeout时,超时后 channel 仍开着,goroutine 泄漏 - 用
for i := 0; ; i++无限循环却不响应中断:必须把ctx.Done()接入select,否则无法优雅停止 - 直接返回无缓冲
chan int:当消费者处理慢,生产者会阻塞在ch ,整个 goroutine 挂起;建议用小缓冲(如 <code>make(chan int, 1))或根据下游吞吐动态调整
正确骨架长这样:
立即学习“go语言免费学习笔记(深入)”;
func Fibonacci(ctx context.Context) chan int {
ch := make(chan int, 1)
go func() {
defer close(ch)
a, b := 0, 1
for {
select {
case ch <- a:
a, b = b, a+b
case <-ctx.Done():
return
}
}
}()
return ch
}需要“暂停/恢复”或“带参数重播”?别硬套 generator 思维
Go 的并发模型和 generator 语义不同:generator 是单线程协作式,Go 是多线程抢占式。想实现“调用一次生成一个值,下次再调”这种接口,chan 反而笨重。
这时更合适的是函数闭包 + 状态变量:
- 返回
func() (int, bool),内部维护a, b,每次调用算下一个值,bool表示是否还有 - 如果还要支持重置,返回结构体,带
Next()和Reset()方法 - 若需并发安全(多个 goroutine 同时调用),才加
sync.Mutex或改用原子操作
例如:type Counter struct{ n int } + func (c *Counter) Next() int { c.n++; return c.n },比开 channel 省资源、无调度开销、无泄漏风险。
异步生成 + 背压控制:用 context.Context 和带缓冲 chan 协同
真实场景中,“无限”只是逻辑上的,下游处理能力有限,必须防止生产端狂灌导致内存爆炸。
- 缓冲区大小不是拍脑袋定的:设为下游单次处理耗时 × 预期峰值 QPS 的 2–3 倍,宁小勿大
-
select中同时监听ctx.Done()和ch写入,但注意——如果ch满了,写操作会立即进入default分支,此时得判断是该丢弃、重试还是终止 - 不要在生成 goroutine 里做重试逻辑:它会让生产节奏失控;重试应由消费者主动拉取或通过错误 channel 反馈
典型背压模式:
select {
case ch <- value:
// 成功发送
case <-time.After(10 * time.Millisecond):
// 缓冲满且等太久,跳过或记录告警
case <-ctx.Done():
return
}真正难的不是写出能跑的 generator,而是想清楚:这个“无限序列”的边界在哪、谁负责生命周期、下游挂了要不要通知上游、错误是静默丢弃还是传播。这些决策比语法细节重要得多。










