iter.Seq 连接多个数据源时的停止协议
来源:17golang原创
时间:2026-10-10 13:08:14 279浏览 收藏
把多个 iter.Seq[V] 连接成一条序列,最稳妥的默认方案是顺序串联:当前数据源完全结束后再进入下一个;一旦下游的 yield 返回 false,组合器立刻返回,不再打开后续数据源。停止协议只有一条主线——消费者表达“不再需要”,组合器负责传播,数据源负责清理自己的资源。
Go 官方对 iter.Seq 的定义正是 func(yield func(V) bool)。序列向 yield 交付值;返回 true 表示继续,返回 false 表示停止。连接多个源时,不能吞掉这个布尔值,也不能把空数据源误判成全局停止。
顺序 Concat 适合什么场景
顺序串联适合“来源有先后优先级、元素类型一致、无需并发交错”的任务。例如先读内存缓存,再读本地文件,最后读取远端分页;或者把多个分片按固定顺序暴露给同一个消费者。它保留惰性:消费者没有走到某个源,该源就不需要创建连接或分配缓冲区。
如果目标是同时等待多个实时流,顺序 Concat 并不合适。那是并发扇入问题,需要额外定义顺序、取消、错误和背压,而不仅是把几个 Seq 放进切片。

最小写法:嵌套 range 直接传播停止
最小实现只需要两层 range。外层选择数据源,内层读取当前源。关键不是循环本身,而是 yield(v) 返回 false 后必须直接 return,这样后续数据源不会启动。
package sequtil
import "iter"
// Concat 按参数顺序连接多个序列。
func Concat[V any](sources ...iter.Seq[V]) iter.Seq[V] {
return func(yield func(V) bool) {
for _, source := range sources {
// 当前源为空时自然进入下一个源,不代表全局停止。
for value := range source {
if !yield(value) {
// 下游不再需要数据,后续源也不能启动。
return
}
}
}
}
}
这段写法不需要额外的 done 通道。消费者在 for value := range combined 中执行 break 时,编译器生成的 yield 会向组合器返回 false;组合器随即返回,当前源也会收到停止信号并退出。这个传播链要求每一层都尊重 false。
停止信号只认 yield 的返回值
一个常见错误是调用 yield 后忽略结果。代码看似还能输出元素,但下游提前退出后,上游会继续读取文件、翻页或发送网络请求。另一个错误是当前源结束时直接返回组合器,这会让第一个空源截断整个序列。
// BadConcat 展示两个容易混淆的错误,不能用于生产代码。
func BadConcat[V any](sources ...iter.Seq[V]) iter.Seq[V] {
return func(yield func(V) bool) {
for _, source := range sources {
for value := range source {
// 错误:忽略 false 会破坏提前停止协议。
yield(value)
}
// 错误:一个源结束不等于所有源都结束。
return
}
}
}
Seq 本身没有独立的 stop 方法。对于推送式迭代器,yield(false) 就是停止接口。只有把 Seq 转换成拉取式迭代器后,才会得到显式的 stop 函数。

资源释放应该写在每个数据源内部
Concat 不知道源里持有什么资源,因此不应替文件源关闭网络响应,也不应猜测某个 goroutine 的终止方式。资源型序列应在自身调用边界内完成申请与释放。只要组合器正确传播 false,源函数返回时的 defer 就会执行。
package main
import (
"bufio"
"iter"
"os"
)
// FileLines 惰性读取文本行,打开与关闭都由源自身负责。
func FileLines(path string) iter.Seq[string] {
return func(yield func(string) bool) {
file, err := os.Open(path)
if err != nil {
// 本例聚焦停止协议;真实项目应使用 Seq2 传递错误。
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if !yield(scanner.Text()) {
// 消费者提前停止,返回后立即执行文件关闭。
return
}
}
}
}
上例为了保持 Seq[string] 简洁,省略了错误通道。需要区分打开失败、扫描失败和正常结束时,可改为 iter.Seq2[string, error],并在错误出现后停止继续产出;不要把错误悄悄当成空序列。
什么时候改用 iter.Pull
iter.Pull 把推送式 Seq 转成 next 与 stop。它适合需要交替读取两个序列、比较相邻来源、做归并排序或由调用方主动决定下一次从哪个源取值的场景。代价是停止责任变得显式:只要 next 还没有返回 false,调用方不再读取时就必须调用 stop。官方文档建议通常直接 defer stop()。
// FirstFromEach 从每个源最多取一个值,展示 Pull 的清理要求。
func FirstFromEach[V any](sources ...iter.Seq[V]) []V {
result := make([]V, 0, len(sources))
for _, source := range sources {
next, stop := iter.Pull(source)
value, ok := next()
if ok {
result = append(result, value)
}
// 当前循环立即结束拉取,不能把 stop 延迟到整个函数末尾。
stop()
}
return result
}
如果在循环里写 defer stop(),所有停止动作会堆到函数返回时才执行,资源占用时间可能明显变长。可以像上例一样在本轮结束时直接调用,或者把单次拉取封装进小函数并在小函数中使用 defer。
三种连接方式怎么选
| 方案 | 适用条件 | 停止方式 | 主要代价 |
|---|---|---|---|
| 顺序 Concat | 固定顺序、惰性读取 | 逐层传播 yield(false) | 不能并行交错多个源 |
| iter.Pull | 交替读取、归并、对齐比较 | 未读完时调用 stop | 调用方承担显式清理 |
| 先收集再拼接 | 数据量小、需要随机访问 | 收集完成后无迭代停止问题 | 失去惰性并占用额外内存 |
默认优先顺序 Concat。只有当消费者必须控制“下一次读哪个源”时,再引入 Pull;只有当数据规模小且后续确实需要索引、排序或重复遍历时,才先收集到切片。并发实时流则应单独设计取消和背压协议,不要把它伪装成普通 Concat。
验收停止协议的四个检查点
- 消费者在第一个源中途
break后,当前源的清理函数会执行。 - 提前停止后,第二个及后续数据源没有被打开或调用。
- 某个空源只会被跳过,不会阻止后续非空源产出。
- 使用
iter.Pull且未读到结尾时,所有路径都会调用stop。
几个容易继续追问的问题
Concat 能否忽略 nil Seq? nil 函数被调用会 panic。若业务允许 nil,组合器应显式跳过;更推荐在构造源列表时消除 nil,让类型边界更清楚。
源发生 panic 怎么处理? Seq 不会自动吞掉 panic。资源型源的 defer 仍会按 Go 规则执行,是否恢复应由更外层的故障边界决定。
多个源元素类型不同怎么办? 先映射成统一领域类型,再传给 Concat;不要为方便而把所有值降成 any,否则停止之外还会引入运行时类型判断。
官方资料在哪里? 可参考 Go iter 包文档和 Go Blog:Range Over Function Types。
-
234 收藏
-
346 收藏
-
131 收藏
-
185 收藏
-
265 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习