用 std::filesystem::file_time_type 轮询检测配置文件修改不可靠;因文件系统 mtime 精度限制(如 ext4 默认 1 秒),且存在覆盖后时间戳不变等风险,需辅以内容哈希校验,建议轮询间隔 ≥100ms。

用 std::filesystem::file_time_type 轮询检测配置文件修改是否可靠?
在无外部信号支持的场景下,轮询是最直接的方案,但“可靠”取决于你对延迟和资源的容忍度。Linux 下 stat() 获取的 mtime 精度通常是 1 秒(ext4 默认),而 std::filesystem::last_write_time() 返回的 file_time_type 在 C++20 中可精确到纳秒 —— 但底层仍受限于文件系统实际精度,不是所有平台都返回真纳秒值。
- 轮询间隔建议 ≥ 100ms,太短会显著增加
stat()系统调用开销;太长则响应滞后 - 注意时区无关性:
last_write_time()返回的是 UTC 时间点,比较前无需转换 - 别只比对时间戳 —— 文件可能被覆盖后 mtime 不变(如
cp --preserve=timestamps),加一层内容哈希(如 CRC32 前 1KB)更稳妥 - Windows 上需用
GetFileTime()配合FILE_NOTIFY_CHANGE_LAST_WRITE才能避免轮询,纯 C++20 标准库无法触发通知
Linux 下用 inotify + signalfd 实现零轮询监听
这是最轻量、响应最快的方式,但必须手动封装系统调用,标准库不提供抽象。核心是让 inotify 事件转成可 read() 的文件描述符,再用 signalfd 把它接入线程的信号处理流 —— 这样一个线程就能同时等待信号和文件事件。
- 先调用
inotify_init1(IN_CLOEXEC)创建 inotify 实例 - 用
inotify_add_watch(fd, "/path/to/conf", IN_MODIFY | IN_MOVED_TO)监听配置目录或具体文件 - 调用
signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK)创建信号接收 fd,把SIGIO加入mask - 关键一步:给 inotify fd 设置
F_SETOWN(getpid())和F_SETFL(FNDELAY | FASYNC),触发内核投递SIGIO - 之后
read()那个signalfdfd 就能拿到事件结构体,无需阻塞或轮询
注意:SIGIO 是不可靠信号,多个事件可能合并为一次通知,必须循环 read() 直到 EAGAIN。
跨进程配置热更为什么不用 shm_open() + mmap()?
共享内存看似高效,但配置项动态生效的本质是「通知+加载」,不是「实时共享」。直接 mmap 一份配置内存,会导致多个进程看到不同步的中间状态 —— 比如写入一半时被读取,或旧进程还在读已释放的页。
立即学习“C++免费学习笔记(深入)”;
-
shm_open()适合高频小数据共享(如计数器),不适合结构化配置(JSON/YAML 解析需完整缓冲区) - 没有修改通知机制:A 进程更新 shm 后,B 进程完全不知道该重解析,仍要额外走信号或文件事件来触发
- 权限和生命周期难管理:
/dev/shm/xxx文件残留、权限错配、O_EXCL竞态等问题频发 - 真正简化方案是:用 inotify 或信号通知“配置变了”,再由各进程各自
fopen()+ 解析本地文件 —— 数据一致性由文件原子写保证(如先写conf.tmp再rename())
Windows 上绕过 ReadDirectoryChangesW 的替代思路
Win32 API 的目录监控接口复杂且容易漏事件(尤其符号链接、重命名场景),如果不想深入 HANDLE 和 OVERLAPPED,可以用更稳的折中法:利用系统定时器 + 单次异步 I/O。
- 创建一个
WaitForSingleObject可等待的 timer queue timer,周期设为 500ms - 每次触发时,用
CreateFile()打开配置文件(带FILE_FLAG_NO_BUFFERING减少干扰),读取前 64 字节做快速校验(比如检查 JSON 开头或版本字段) - 不依赖
GetFileAttributesEx()的ftLastWriteTime,因为 NTFS 时间戳可能因时区或夏令时异常跳变 - 若校验失败(长度突变、magic 字节不符),才全量读取并解析 —— 避免每次定时器都触发完整解析
- 此法比
ReadDirectoryChangesW更易调试,错误时直接GetLastError()就能定位
真正的难点不在监听,而在配置变更后如何安全替换运行时对象:比如一个正在被多线程访问的 std::shared_ptr<config></config>,必须用原子指针交换(std::atomic_store_explicit)并确保旧对象生命周期可控 —— 这部分比 IPC 机制本身更容易出错。










