正确方式是整行读取后字符串切分:用 open()+read() 读最多 64 字节,strtok_r 拆出 5 个 token,前三个转 double,第四个用 strchr 分割,第五个用 strtoul 转 unsigned long。

读取 /proc/loadavg 的正确方式:别用 fscanf 直接扫四次
Linux 的 /proc/loadavg 是个只读伪文件,内容形如 0.12 0.08 0.03 1/123 4567,共 5 个字段。常见错误是打开后用 fscanf(fd, "%f %f %f %s %d", ...) 一把梭——但第四个字段(1/123)含斜杠,%s 会截断;第五个字段是进程 ID(不是启动时间),且可能溢出 int(现代系统 PID 可超 215)。更稳的做法是整行读 + 字符串切分:
- 用
std::ifstream或open()+read()读整行(最多 64 字节足够) - 用
std::stringstream或strtok_r按空格拆,确保拿到全部 5 个 token - 前三个字段转
double(负载值),第四个用strchr找'/'分割运行中进程数和总进程数,第五个用strtoul转unsigned long
std::ofstream 追加写日志时的缓冲陷阱
直接 ofstream.open("/var/log/load.log", ios::app) 看似合理,但默认全缓冲 —— 若程序意外退出(比如被 SIGTERM 中断),最后几条日志大概率丢失。关键不是“要不要 flush”,而是“什么时候 flush”:
- 每写一行后调
os 开销大,尤其高频采样(如 1s 一次) - 改用
setvbuf对底层FILE*设为行缓冲(_IOLBF),或用std::ios_base::unitbuf标志(os.setf(std::ios::unitbuf)) - 更实用的是:每写满 10 行或间隔 5 秒主动
flush(),兼顾性能与可靠性 - 注意日志路径权限:
/var/log/通常需 root,普通用户建议写到$HOME/.local/log/
实时性保障:别用 sleep(1) 控制采样间隔
负载本身是 1/5/15 分钟指数衰减平均值,本身不“实时”,但你的采集节奏得稳定。用 sleep(1) 会导致 drift —— 读文件、解析、写磁盘耗时叠加后,实际间隔可能是 1.03s、1.07s……长期下来时间戳偏移明显:
- 用
clock_gettime(CLOCK_MONOTONIC, &ts)记录每次循环开始时间 - 下一轮等待时计算
next = last + 1e9(纳秒),再用nanosleep(&req, &rem)补足差值 - 如果某次处理超时(比如磁盘卡顿),跳过本次记录,避免堆积 —— 负载日志宁可丢点样本,也不要时间错乱
- 避免在循环里做复杂格式化:把
std::to_string和strftime提前算好,减少临界区耗时
权限与稳定性:/proc/loadavg 不会返回错误,但你的代码会
/proc/loadavg 对所有用户可读,几乎不会 open() 失败。真正容易崩的是你自己的逻辑:
- 忘记检查
read()返回值 —— 返回 0 表示 EOF(正常),-1 才是错误;返回正数但没读满一行?说明内核刚好在写入中途被截断(极罕见但可能),此时应丢弃该次数据 - 日志目录不存在时
ofstream::open静默失败,必须用os.is_open()判断 - 长时间运行要防磁盘写满:写入前用
statvfs()检查剩余空间,低于 100MB 就停写并 warn 到 stderr - 别用
std::endl—— 它等于\n + flush,纯属自找麻烦
最常被忽略的其实是信号安全:如果用 signal(SIGINT, handler) 做优雅退出,handler 里只能调 async-signal-safe 函数(write() 可以,std::cout 不行)。真要处理信号,优先选 sigwait() 配合 pthread_sigmask()。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











