go生成器需用channel或iter.seq[t]实现;chan t最常用但易卡死:需close避免range阻塞,需done通道防goroutine泄漏,不支持多消费者共用。

Go 没有 yield 关键字,所谓“生成器函数”只能靠闭包 + channel 或 iter.Seq[T] 两种路径实现;选哪条取决于你要同步拉取还是异步生产——混用会出 bug。
chan T 是最常用但最容易卡死的生成器模式
用 goroutine 启动一个生产者,把结果发到 chan T,调用方用 for v := range ch 消费。这是最直觉的做法,但有三个硬伤:
- 生产者 goroutine 必须在最后调用
close(ch),否则消费者永远阻塞在range - 消费者中途
break或 panic,生产者 goroutine 就泄漏了(没地方通知它停) - 多个消费者不能共用同一个
chan T,因为通道是单向消费的
典型修复写法是加一个 done chan struct{},生产者用 select 监听:case 。但这就不再是“简单生成器”,而成了带控制协议的协程管理。
iter.Seq[T] 是 Go 1.22+ 推荐的轻量拉取式生成器
iter.Seq[T] 不是通道,也不是 goroutine,它是一个函数类型:func(yield func(T) bool) error。调用它时传入一个 yield 函数,你手动调用 yield(val) 推送值;如果 yield 返回 false(比如消费者 break 了),你就立刻退出——不启 goroutine、不分配 channel、零调度开销。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 递归遍历树?直接在闭包里深搜,每次找到节点就
yield(node) - 读文件行?先用
bufio.Scanner一行行读,每行调一次yield - 绝对不要在里面起 goroutine 写
go func() { ch ——这破坏了同步拉取语义,<code>yield可能已被丢弃
示例:func IntsFrom(n int) iter.Seq[int] { return func(yield func(int) bool) error { for i := n; ; i++ { if !yield(i) { return nil } } } }
闭包捕获状态时,变量逃逸到堆上是常态
生成器逻辑常需维护中间状态(比如当前索引、已访问节点集合)。Go 编译器会通过逃逸分析自动把被闭包引用的局部变量挪到堆上——这不是 bug,是必须行为。你可以用 go build -gcflags="-m" main.go 确认是否逃逸。
- 循环中创建多个闭包却共享同一个变量?常见于
for i := range chs { go func() { ... i ... }() }——所有 goroutine 最终看到的都是最后一个i值 - 修复方式:传参绑定,写成
go func(idx int) { ... idx ... }(i) - 闭包内修改状态(如计数器)本身线程不安全;并发调用同一闭包需自行加
sync.Mutex或用atomic
什么时候该用 channel,什么时候该用 iter.Seq[T]?
核心判断只有一条:数据源是否天然异步?
- 是:比如从网络流、数据库游标、传感器实时采样读数据 → 用
chan T,配合done控制生命周期 - 否:比如遍历内存 slice、生成斐波那契数列、DFS 遍历结构体 → 用
iter.Seq[T],更轻、无 goroutine 开销、天然支持中断
别试图用 iter.Seq[T] 包一层 chan T 消费逻辑——这等于把同步协议套在异步通道上,既没解决泄漏问题,又引入了额外分配和调度延迟。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










