generator 是 go 中模拟 python yield 的惰性求值模式,用 channel + goroutine 解耦生成与消费逻辑,避免内存溢出和 goroutine 泄漏;需支持取消、防止缓冲区溢出与未消费卡死。

Generator 是什么,它解决什么问题
Go 里没有原生 Generator(像 Python 的 yield),但可以用 channel + goroutine 模拟出“按需生产、惰性求值”的行为。它不是语法糖,而是一种模式——核心目标是把「数据生成逻辑」和「消费逻辑」解耦,避免一次性构造大 slice 或阻塞等待全部结果。
常见错误现象:runtime: goroutine stack exceeds 1GB limit,往往是因为用递归或循环提前生成了百万级元素再传 channel;或者 consumer 还没 ready,producer 就疯狂发数据导致 channel 缓冲区爆掉。
- Generator 本质是一个返回
chan T的函数,内部启动 goroutine 向该 channel 写入数据 - 调用方只管从 channel 读,不关心怎么生成、何时生成
- 必须注意:未被消费的 channel 会卡住 goroutine,造成泄漏
如何写一个安全的 Generator 函数
关键不是“能跑”,而是“不泄漏、不 panic、可中断”。下面是一个带取消支持的标准写法:
func IntRange(start, end int) <p>但更推荐用 <code>context.Context</code> 控制生命周期:</p>
- 用
context.WithCancel或context.WithTimeout包裹 generator 调用 - 在 goroutine 内部用
select监听ctx.Done(),及时退出 - 不要直接在 generator 外部调用
close(ch),必须由内部 goroutine 关闭 - channel 类型声明为
(只读),防止误写
多个 Generator 组合时为什么容易 panic
比如想把两个整数序列拼起来:ch1 := IntRange(0, 3); ch2 := IntRange(10, 13),然后用 for range 依次读取——这会卡在第一个 channel 关闭后,第二个还没开始读。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
常见错误写法:
for v := range ch1 { fmt.Println(v) }
for v := range ch2 { fmt.Println(v) }
正确做法是用 fan-in 模式合并 channel:
- 单独起 goroutine 分别从
ch1和ch2读,并转发到同一个输出 channel - 或用第三方库如
golang.org/x/exp/slices(Go 1.21+)配合slices.Concat预加载(仅适合小数据) - 更通用的是用
reflect.Select实现多路复用,但复杂度高,一般用select+default配合非阻塞读更可控
Generator 和 sync.Pool / bufio.Scanner 的边界在哪
Generator 适合「逻辑上无限/流式/异步生成」场景,比如日志行解析、数据库游标分页、实时传感器采样。但它不适合替代以下东西:
-
sync.Pool:用于对象复用,减少 GC;Generator 不管理内存复用 -
bufio.Scanner:已内置缓冲与行切割,且默认带MaxScanTokenSize保护;自己手写 Generator 容易忽略行截断、编码异常等细节 -
sql.Rows:本身已是惰性迭代器,强行包装成 Generator 只会增加 goroutine 开销和关闭时机混乱风险
真正需要 Generator 的时刻,往往是「你得自己控制数据源头节奏」,比如从 WebSocket 流中按业务规则拆包,或把多个 API 分页响应 merge 成单一流。这时候,chan 的背压能力(consumer 慢 → producer 自然减速)才是关键价值。
别忘了:每个 Generator 都隐含一个 goroutine,数量一多就成性能瓶颈。真要高吞吐,优先考虑基于 buffer 的批量处理,而不是无节制地开 goroutine。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










