Linux下最轻量可靠的文件监控方式是inotify,需监听IN_MODIFY、IN_MOVED_TO和IN_CREATE以覆盖编辑器重写场景,注意fd限制、事件读取完整性及跨平台封装建议。

用 inotify 监控文件变化(Linux)
Linux 下最轻量、最可靠的方式就是 inotify,它不轮询,内核级事件通知,延迟低且省资源。glibc 本身不封装它,得直接调 inotify_init()、inotify_add_watch() 这些系统调用。
常见错误是只监听 IN_MODIFY,结果配置文件被编辑器重写(先写临时文件再 rename())时收不到通知——必须加 IN_MOVED_TO 和 IN_CREATE,尤其要 catch rename() 场景。
- 只监听单个文件?用
IN_MASK_ADD配合IN_MODIFY | IN_MOVED_TO即可;若监控整个目录(比如支持热插拔配置片段),还得加上IN_ONLYDIR和递归处理逻辑 -
inotify实例有 fd 限制,默认可能只有 8192,高并发多配置文件时容易inotify_init(): Too many open files,需提前调ulimit -n或改/proc/sys/fs/inotify/max_user_instances - 事件读取必须用
read()循环读完全部 pending 事件,否则残留事件会丢失;每次read()返回的是变长结构体流,不能当固定大小 buffer 处理
Windows 上用 ReadDirectoryChangesW 替代
Windows 没 inotify,等价方案是 ReadDirectoryChangesW,但它只能监听目录,不能直接监单个文件——所以得把配置文件放在专用目录里,比如 ./conf/,然后监听该目录。
典型翻车点:传入的 lpOverlapped 忘了初始化,或没配 FILE_FLAG_BACKUP_SEMANTICS,导致对目录句柄调用失败;还有人用同步模式阻塞等待,卡死主线程。
- 必须用异步 I/O +
OVERLAPPED结构,配合GetQueuedCompletionStatus()或 I/O 完成端口(IOCP)处理,别用同步轮询 - 事件类型里重点关
FILE_NOTIFY_CHANGE_LAST_WRITE(内容改)和FILE_NOTIFY_CHANGE_NAME(重命名/新建),编辑器保存行为基本就靠这两个覆盖 - 返回的
FILE_NOTIFY_INFORMATION是链表结构,需按NextEntryOffset字段手动遍历,不是一次性 memcpy 就完事
配置加载时机与线程安全怎么搞
文件一变就立刻解析?危险。编辑器保存瞬间文件可能是半截状态(比如 vim 的 .swp 机制或 VS Code 的原子写),直接 load 会解析失败甚至 crash。必须加校验和重试逻辑。
更关键的是:配置变量常被多线程读,而 reload 是写操作。不能裸用全局指针交换,得用原子指针或读写锁。
- 推荐做法:每次 reload 新建一个配置对象(如
std::shared_ptr),解析成功后再用std::atomic_store()原子更新全局指针;读配置的地方用std::atomic_load()获取快照,避免读到中间态 - 解析前先
stat()检查 mtime 是否真变了,再open(O_RDONLY)读取,读完再fstat()对比 size/mtime,双重确认——防编辑器“假触发” - 不要在 inotify 回调里直接做 heavy parse(如 YAML/JSON 解析),应把事件投递到工作线程池,避免阻塞事件循环
跨平台封装建议:别自己写大轮子
如果你项目要同时跑 Linux/Windows/macOS,硬刚三套 API 很累。已有成熟小库可直接用:inotify-tools 是命令行工具,不适用;但 libuv 的 uv_fs_event_t 就是干这事的,C++ 封装也好做;或者更轻量的 watchman C API(Facebook 开源,支持 macOS FSEvents),不过引入依赖略重。
自己封装时最容易忽略的是信号安全:inotify 事件回调若在 signal handler 里触发(比如用了 signalfd),那所有非 async-signal-safe 函数都不能调——但你不可能在信号上下文里 new 对象或调 std::regex。所以事件分发必须出 signal 域。
-
libuv的uv_fs_event_start()内部已处理好跨平台差异和线程模型,回调函数运行在 event loop 线程,安全可用 - 如果坚持零依赖,至少把 inotify / ReadDirectoryChangesW 封装成统一接口类,提供
start_watching(const std::string& path)和on_change(std::function,别让业务代码感知底层&& cb) - macOS 的
FSEvents是异步且延迟略高(秒级),不适合毫秒级敏感场景,测试时别拿它对标 inotify 表现
真正难的不是监听,而是怎么定义“一次有效修改”:文件写入完成、内容合法、新旧配置能平滑切换——这些边界条件,比选哪个 API 要命得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











