应使用 sync.WaitGroup 同步 goroutine:启动前 wg.Add(1),函数末尾 wg.Done(),主 goroutine 调用 wg.Wait();并发控制用带缓冲 channel 作信号量,如 sem := make(chan struct{}, 10)。

goroutine 启动后文件扫描不等待就退出?
Go 程序启动 go scanFile(filepath) 后主 goroutine 立即返回或退出,导致子 goroutine 被强制终止。根本原因是缺少同步机制——Go 不会自动等待未完成的 goroutine。
正确做法是用 sync.WaitGroup 显式跟踪任务数量:
- 在启动每个
go scanFile()前调用wg.Add(1) - 在
scanFile函数末尾(无论成功失败)调用wg.Done() - 主 goroutine 在所有
go启动后,调用wg.Wait()阻塞等待
别用 time.Sleep 模拟等待——它不可靠,且掩盖了同步缺失的本质问题。
如何限制并发数避免 open too many files 错误?
直接对每个文件起一个 goroutine,遇到几千个文件时极易触发系统级错误:too many open files。这不是 Go 问题,而是操作系统对进程打开文件描述符数量的硬限制(通常 1024)。
立即学习“go语言免费学习笔记(深入)”;
必须引入并发控制,推荐用带缓冲的 channel 作为计数信号量:
sem := make(chan struct{}, 10) // 最多同时处理 10 个文件
for _, path := range files {
sem <- struct{}{} // 获取令牌
go func(p string) {
defer func() { <-sem }() // 归还令牌
scanFile(p)
}(path)
}注意:必须用 func(p string) 显式传参,否则闭包捕获循环变量 path 会导致所有 goroutine 扫描最后一个路径。
scanFile 中读取文件内容时 panic: invalid memory address?
常见于用 ioutil.ReadFile(Go 1.16+ 已弃用)或 os.ReadFile 后直接操作返回的字节切片,但未检查错误。一旦文件不存在、权限不足或被删除,err != nil,而 data 是 nil,后续 strings.Contains(data, "keyword") 就 panic。
必须始终先判错:
data, err := os.ReadFile(path)
if err != nil {
log.Printf("skip %s: %v", path, err)
return
}
// 此时 data 才可安全使用另外,大文件(如 >100MB)不宜全量读入内存,应改用 bufio.Scanner 流式处理,否则可能触发 OOM。
为什么 filepath.WalkDir 比 filepath.Walk 更适合并发扫描?
filepath.Walk 是同步阻塞调用,内部递归遍历目录树,无法在遍历中途插入 goroutine;而 filepath.WalkDir(Go 1.16+)支持在回调中返回 filepath.SkipDir 或直接并发启动扫描,更灵活。
典型模式是:用 WalkDir 收集文件路径到切片,再分发给 worker goroutine;或更进一步,在回调里直接做轻量判断(如扩展名过滤),符合条件才发给扫描池:
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil // 忽略权限错误等
}
if !d.IsDir() && strings.HasSuffix(d.Name(), ".log") {
sem <- struct{}{}
go func(p string) {
defer func() { <-sem }()
scanFile(p)
}(path)
}
return nil
})注意 WalkDir 的回调函数本身运行在主线程,不能在其中做耗时操作(如实际读文件),否则会拖慢整个遍历过程。
并发文件扫描真正的难点不在启动 goroutine,而在错误传播、资源回收和边界控制——比如某个文件卡死读取,是否要加超时?日志输出是否需要加锁?扫描结果如何安全聚合?这些细节不处理,程序在真实环境大概率静默失败。


















