
本文介绍如何在 go 中安全、高效地计算目录总大小,重点解决使用全局变量引发的并发安全隐患,并提供基于闭包的优雅实现方案。
本文介绍如何在 go 中安全、高效地计算目录总大小,重点解决使用全局变量引发的并发安全隐患,并提供基于闭包的优雅实现方案。
在 Go 中遍历目录并累加文件大小,看似简单,但若处理不当,极易引入隐蔽的并发 bug 和维护难题。原始代码中将 dirSize 声明为包级全局变量,虽能实现功能,却存在两大严重缺陷: 一是违反封装原则,破坏函数纯度;二是存在竞态条件(race condition)——当多个 goroutine 并发调用 DirSizeMB 时,dirSize 会被交叉修改,导致结果不可预测且难以调试 。
正确的做法是将状态(即累计大小)严格限定在函数作用域内。最简洁、惯用的方式是利用 匿名函数闭包(closure) 捕获局部变量 size,使其在 filepath.Walk 的回调中安全访问和更新:
import ("io/fs" "path/filepath") // DirSize 返回指定路径下所有非目录文件的总字节数,忽略遍历错误(如权限不足)// 若需精确错误控制,可进一步处理 err func DirSize(path string) (int64, error) {var size int64 err := filepath.Walk(path, func(_ string, info fs.FileInfo, err error) error {if err != nil { // 可选择跳过无法访问的条目(如无权限),继续遍历 return nil} if !info.IsDir() { size += info.Size() } return nil }) return size, err }
✅ 关键改进说明 :
- size 是 DirSize 函数内的局部变量,生命周期与调用绑定,完全线程安全;
- 匿名函数通过闭包“捕获”该变量,无需额外参数或结构体;
- 使用 fs.FileInfo(Go 1.16+ 推荐)替代已弃用的 os.FileInfo,提升兼容性;
- 错误处理更健壮:对 err != nil 的情况返回 nil 表示跳过当前项(常见于 permission denied),避免中断整个遍历。
若需返回 MB 单位并支持精度控制(如四舍五入到小数点后两位),可封装转换逻辑:
import "math" func DirSizeMB(path string) (float64, error) {bytes, err := DirSize(path) if err != nil {return 0, err} mb := float64(bytes) / 1024.0 / 1024.0 return math.Round(mb*100) / 100, nil // 四舍五入至小数点后两位 }
⚠️ 注意事项 :
- filepath.Walk 不保证遍历顺序,但对求和无影响;
- 符号链接默认被跟随(Walk 行为),若需跳过软链,应改用 filepath.WalkDir + 自定义 fs.DirEntry 判断;
- 对超大目录,考虑添加上下文(context.Context)支持超时或取消;
- 生产环境建议增加日志或进度反馈,便于排查长时间阻塞。
综上,摒弃全局状态、拥抱闭包与局部作用域,是编写可维护、可并发 Go 代码的基本准则。本方案简洁、安全、符合 Go idioms,可直接集成至工具类或 CLI 应用中。