看到“too many open files”报错、服务突然拒绝新连接、日志写不进磁盘,基本就是文件描述符泄露——根本原因是某个os.File打开后未关闭,且泄漏随时间累积直至进程被系统掐断。

怎么确认是文件描述符泄露,不是别的问题
看到 too many open files 报错、服务突然拒绝新连接、日志写不进磁盘,基本就是它了——不是并发太高,也不是系统 ulimit 太低,而是某个 os.File 打开了没关,且这个泄漏会随时间累积,直到进程被系统掐断。别急着调大 ulimit,那只是延缓暴露。
查当前进程开了多少 fd 和哪些路径在反复打开
生产环境不一定装 lsof,优先用 /proc 直接读:
-
ls -l /proc/—— 看总数(含符号链接,略偏高但趋势可靠)/fd/ | wc -l -
ls -l /proc/—— 看前 15 个打开项,高频重复的/fd/ 2>/dev/null | grep -E 'REG|socket|pipe' | head -15 /tmp/xxx或/var/log/xxx.log就是重点怀疑对象 - 如果发现大量
anon_inode:[eventpoll]或socket:[...],可能是http.Client连接池或fsnotify实例没释放
为什么 defer f.Close() 有时等于没写
defer 只保证函数返回时执行,而很多场景下函数根本不会返回:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- goroutine 挂起:比如在 handler 里启了个 goroutine 去读文件,但 channel 没人收、
select没default,这个 goroutine 永远不退出,defer就永远不会触发 -
panic被recover捕获后继续跑逻辑,旧*os.File变量被覆盖,新文件又打开,旧句柄就悬空了 - 循环变量误用:在
for range files中直接go func() { f, _ := os.Open(name); defer f.Close() }(),所有 goroutine 共享同一个name变量,最终可能多个 goroutine 尝试关同一个f,或某个f根本没被关 -
f是nil:os.Open失败时返回nil, err,此时defer f.Close()会 panic;正确写法是先判错再defer
批量文件处理时怎么避免高频 Open/Close
不是靠复用 *os.File(sync.Pool 不适用),而是减少打开次数:
- 小文件用
os.ReadFile:它内部自动Open+Read+Close,语义清晰且无泄漏风险 - 大文件流式处理用
bufio.NewReader+ 固定 buffer(如 4KB),避免反复分配内存,同时确保每次file.Read后检查err == io.EOF并显式file.Close() - 长期需要读写的文件(如配置、轮转日志目标),用单例 +
sync.RWMutex管理,只在变更时重新Open,旧句柄Close - 临时文件必须用
os.CreateTemp,业务逻辑结束立即os.Remove,别等 GC——文件句柄不归 GC 管
真正麻烦的从来不是“怎么关”,而是“什么时候该关”和“谁负责关”。尤其在并发、错误分支、recover 后续逻辑交织的场景里,defer 很容易失效,而 /proc/ 下那些重复出现的路径,才是最诚实的线索。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










