linux用inotify监控需结合epoll/select避免轮询,正确解析多事件及cookie配对;windows需异步iocp,注意路径格式与缓冲区;跨平台应暴露底层事件而非强行统一语义。

Linux下用inotify监控文件变化,别直接裸写循环
inotify本身不阻塞,但read()返回的事件需要自己解析,裸写while循环容易漏事件或卡死。核心是把inotify_fd放进epoll或select里等就绪,而不是反复read()轮询。
- 每次
read()可能返回多个struct inotify_event,要按event->len偏移累加解析,不能只取第一个 -
IN_MOVED_FROM和IN_MOVED_TO必须配对看cookie字段,否则重命名会被当成删除+新建 - 监控目录时,子目录里的文件变动不会自动上报,得递归调用
inotify_add_watch(),且注意IN_CREATE | IN_ISDIR捕获新目录后立刻加监 - 内核inotify实例有上限(
/proc/sys/fs/inotify/max_user_watches),大项目建议按需增配,别一上来就全目录递归
Windows上ReadDirectoryChangesW必须配合Overlapped I/O
同步模式下ReadDirectoryChangesW()会阻塞线程,且单次调用只返回一批事件,不设超时就永远卡住。真实项目必须用异步I/O + GetQueuedCompletionStatus()或IOCP。
- 缓冲区至少4KB,太小会导致
ERROR_NOTIFY_ENUM_DIR错误,事件被截断 -
lpOverlapped必须指向堆内存(栈变量在函数返回后失效),且每个监控需独立OVERLAPPED结构 - 监控路径要用
\?C:path格式绕过MAX_PATH限制,普通C:开头路径在深层嵌套时会失败 - 返回的
FILE_NOTIFY_INFORMATION是变长结构,遍历要用NextEntryOffset跳转,不是简单指针++
跨平台封装的关键不是抽象API,而是统一事件语义
inotify的IN_DELETE_SELF和Windows的FILE_ACTION_REMOVED对“监控目录被删”的处理逻辑完全不同:前者watch自动失效,后者仍能收到后续事件直到句柄关闭。强行统一成“目录消失”会掩盖行为差异。
- 优先暴露底层事件类型(如
IN_MOVED_TO、FILE_ACTION_ADDED),再由上层业务决定是否合并 - 路径字段在Windows返回的是相对路径(相对于监控句柄),inotify是相对于watch添加时的路径,拼接完整路径时逻辑不同
- inotify没有“文件被其他进程修改”的精确通知(
IN_MODIFY粒度是写操作,非内容变更),Windows的FILE_ACTION_MODIFIED也仅表示属性/时间戳改,二者都不能替代文件内容校验
不要用std::filesystem::last_write_time轮询做“轻量替代”
看似简单,但每秒扫一次目录,1000个文件就是1000次系统调用,CPU和IO开销远超inotify/IOCP。更糟的是,两次轮询之间发生的快速创建-删除-重建,会彻底丢失事件。
立即学习“C++免费学习笔记(深入)”;
- 轮询间隔设成100ms也没用——
last_write_time()精度在Windows是100ns但实际更新延迟常达15ms,Linux ext4默认是秒级 - 如果真要兜底,只对单个关键文件用
stat()/GetFileAttributesEx()检查mtime,别扫目录树 - 某些容器环境(如Docker Desktop for Mac)挂载卷不支持inotify,此时轮询是唯一选择,但必须明确标注为降级模式
真正的难点从来不是调哪个函数,而是怎么让事件流不丢、不错、不重复,以及当监控路径被mv/rm/rename时,你的回调是否还持有有效句柄或路径引用。







