fsnotify 默认不支持深度监听,需手动递归注册子目录;watcher.add("dir") 仅监听目录自身元数据变更,不监控其子内容;各平台机制不同:linux inotify 每 watch 占一 fd,macos kqueue 深度超限触发溢出,windows readdirectorychangesw 默认仅一级;启动时应 filepath.walkdir 遍历并 add 所有子目录,跳过符号链接;运行时需监听 create 事件动态 add 新建目录,并用 mutex 保护;events channel 必须带缓冲以防丢事件。

fsnotify 默认不支持深度监听,必须手动递归注册子目录,否则只收得到父目录自身的元数据变更(比如被重命名),收不到任何子文件改动。
为什么 watcher.Add("dir") 收不到子目录里文件的修改
因为 fsnotify.Watcher 的 Add() 方法只注册单个路径的监听句柄,底层系统机制(inotify/kqueue/ReadDirectoryChangesW)本身就不提供“递归开关”。watcher.Add("dir") 实际监听的是 "dir" 这个目录项本身的事件(如被 mv、rmdir),不是它的内容树。
- Linux 下 inotify 每个 watch 占一个 fd,
watcher.Add("dir/a")和watcher.Add("dir/b")是两个独立 watch - macOS kqueue 对嵌套层级过深(>100 层)可能触发
fsnotify.ErrEventOverflow,需捕获并降级处理 - Windows ReadDirectoryChangesW 默认只通知一级子项,要穿透必须显式调用
watcher.Add("dir/a")等子路径
启动时全量注册子目录的正确写法
用 filepath.WalkDir() 遍历,对每个 entry.IsDir() 为 true 的路径调 watcher.Add(path),注意跳过符号链接防止死循环或权限错误。
- 先调
filepath.EvalSymlinks(root)解析真实路径,再取其父目录——避免软链指向不存在路径导致Add()失败 - 遍历时加判断:
if entry.Type()&os.ModeSymlink != 0 { continue },默认不跟随软链 - 不要用
filepath.Walk()(已废弃),它不保证顺序且可能重复进入软链目标 - 注册前检查路径是否存在:
_, err := os.Stat(path),os.IsNotExist(err)则跳过
运行时动态响应新创建的子目录
仅靠启动时注册不够:用户后续 mkdir dir/x/y,你不会自动收到 dir/x/y 的事件。必须在事件循环中监听 fsnotify.Create 并主动 Add()。
- 收到事件后先确认是目录:
if event.Op&fsnotify.Create != 0 && event.IsDir() - 立刻调
watcher.Add(event.Name),但需加sync.Mutex保护,防止多 goroutine 并发Addpanic - 别依赖
strings.HasSuffix(event.Name, "/")判断目录——某些系统返回不带斜杠,应以event.IsDir()为准 - 新增目录后,建议延时 10ms 再
Stat()确认是否真正就绪,避免 race 条件下Add()失败
Events channel 不带缓冲会直接丢事件
watcher.Events 是无缓冲 channel,如果消费速度跟不上生产速度(比如事件爆发、处理逻辑阻塞),内部 goroutine 会卡住,后续事件永久丢失,甚至 watcher 停摆。
- 必须用带缓冲 channel 中转:
events := make(chan fsnotify.Event, 256) - 另起 goroutine 拉取原始
watcher.Events并转发到缓冲 channel:go func() { for e := range watcher.Events { events - 别在
for range watcher.Events里做耗时操作(如ioutil.ReadFile),应只做快速分发 - 务必同时监听
watcher.Errors,no space left on device很可能是/proc/sys/fs/inotify/max_user_watches耗尽,非代码 bug
最易被忽略的一点:所有 Add() 调用都必须发生在 watcher 创建之后、Close() 之前;一旦 Close(),再调 Add() 会 panic,且无法恢复。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











