
本文详解如何在 go 中安全、高效地实现“并发读取文件 + 并行处理”,重点解决因通道未关闭导致的 goroutine 死锁问题,并提供可扩展的 worker pool 设计模式。
在 Go 中实现“并发读取文件”常被误解为多 goroutine 同时读取同一文件——实际上,文件 I/O 本身通常不需(也不应)并发读取;真正的并发价值在于:单协程顺序读取(保证顺序与资源安全),多协程并行处理(提升 CPU 密集型任务吞吐)。你提供的代码已抓住这一核心思想,但卡在了 goroutine 协作的同步细节上:wg.Wait() 阻塞在主线程,而 results 通道未关闭,导致 for v := range results 永远等待,引发死锁。
关键修正在于 解耦三类职责:
✅ 生产者(Producer):单独 goroutine 负责扫描文件 → 写入 jobs 通道 → 完成后关闭 jobs;
✅ 工作者(Workers):多个 goroutine 从 jobs 读取、处理、写入 results;
✅ 消费者(Consumer):单独 goroutine 等待所有 workers 完成(wg.Wait())→ 关闭 results → 主线程安全遍历。
以下是重构后的完整、可运行示例(已移除冗余注释,增强健壮性):
package main
import (
"bufio"
"fmt"
"regexp"
"strings"
"sync"
)
func telephoneNumbersInFile(path string) int {
file := strings.NewReader(path)
telephone := regexp.MustCompile(`\(\d+\)\s\d+-\d+`)
jobs := make(chan string, 10) // 缓冲通道避免生产者阻塞
results := make(chan int, 10)
wg := sync.WaitGroup{}
// 启动 3 个 worker
for w := 1; w <= 3; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for line := range jobs {
if telephone.MatchString(line) {
results <- 1
}
}
}()
}
// 生产者:独立 goroutine 扫描并发送数据
go func() {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
jobs <- scanner.Text()
}
close(jobs) // 关键!通知 workers 输入结束
}()
// 消费者:等待 workers 结束后关闭 results
go func() {
wg.Wait()
close(results) // 关键!使 range results 可正常退出
}()
// 主线程:安全收集结果
counts := 0
for result := range results {
counts += result
}
return counts
}
func main() {
const input = "Foo\n(555) 123-3456\nBar\n(800) 999-0000\nBaz"
fmt.Println("Found", telephoneNumbersInFile(input), "phone numbers") // 输出: Found 2 phone numbers
}✅ 注意事项与进阶建议:
- 缓冲通道很重要:jobs 和 results 使用缓冲(如 make(chan T, 10))可显著减少 goroutine 阻塞,尤其在 worker 处理速度波动时;
- 无需显式 sync.WaitGroup 也可实现:若改用“计数驱动”模型(例如生产者先发总行数 n 到 countCh,消费者只收 n 个结果),可完全避免 sync 包,但会增加协议复杂度;
- 扩展至批量处理:将 jobs 类型改为 chan []string,在生产者中按需 scanner.Scan() 多次后打包发送,worker 内部用 range batch 处理,大幅提升正则匹配的局部性;
- 错误处理不可省略:真实场景中需检查 scanner.Err(),并在 jobs 发送前做空行/编码过滤;
- 资源释放提醒:若处理真实文件,记得用 defer file.Close()(本例用 strings.Reader 无需关闭)。
这套模式是 Go 并发编程的经典范式——通过 channel 明确数据流边界,用 goroutine 划分关注点,以 close 作为协作信号。掌握它,你就能从容应对日志分析、ETL 流水线、配置批量校验等绝大多数 I/O + 计算混合场景。









