最直接方式是clock_gettime(clock_thread_cputime_id, &ts),返回线程自启动以来用户态与内核态总cpu时间(纳秒级);若需拆分二者,则须读取/proc/self/task/[tid]/stat的第14、15字段(utime/stime jiffies)并转换。

Linux 下用 clock_gettime(CLOCK_THREAD_CPUTIME_ID, …) 获取线程 CPU 时间
这是最直接、开销最小的方式,能同时拿到用户态 + 内核态的总和(即线程实际消耗的 CPU 时间)。CLOCK_THREAD_CPUTIME_ID 是 POSIX 标准支持的时钟类型,但仅在 Linux 和部分类 Unix 系统可用,Windows 不支持。
常见错误是传入了错误的时钟 ID,比如误用 CLOCK_MONOTONIC 或 CLOCK_PROCESS_CPUTIME_ID —— 前者是墙上时间,后者返回的是整个进程的累计 CPU 时间,不是当前线程。
实操建议:
- 必须链接
-lrt(尤其在较老的 glibc 版本中) - 调用前检查返回值:失败时
clock_gettime返回 -1,errno可能为EINVAL(内核不支持该 clock ID)或EFAULT(tp指针非法) - 示例片段:
struct timespec ts; if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) { uint64_t nanos = ts.tv_sec * 1000000000ULL + ts.tv_nsec; }
需要拆分用户态 / 内核态?读取 /proc/self/stat
/proc/self/stat 是唯一稳定获取线程级用户态(utime)与内核态(stime)时间(单位:jiffies)的通用方法。注意:它反映的是**调用时刻该线程自创建以来的累计值**,不是瞬时速率。
容易踩的坑:
- 字段位置依赖内核版本,但第 14 项(
utime)和第 15 项(stime)自 2.5+ 内核起已稳定;需跳过括号内的进程名(可能含空格) - 必须用
pthread_self()构造路径如/proc/self/task/<tid>/stat</tid>才能获取**当前线程**而非主线程的数据;/proc/self/stat实际对应主线程 - jiffies 需转为纳秒:先除以
sysconf(_SC_CLK_TCK)得秒数,再乘 1e9;直接硬编码HZ=100会出错(现代内核常为 250 或 1000)
为什么 getrusage(RUSAGE_THREAD, …) 不推荐
getrusage 的 RUSAGE_THREAD 选项理论上可分离用户/系统时间,但实际支持极差:glibc 2.27+ 才开始有限实现,且多数发行版内核(包括主流 5.x/6.x)仍返回 ENOSYS。即使成功,其精度低于 clock_gettime,且字段含义易混淆(ru_utime 和 ru_stime 是 struct timeval,非原子读取)。
除非你明确控制部署环境(如定制内核 + 新版 glibc),否则不要把它当作可行方案。线上服务遇到 getrusage 返回 -1 且 errno == ENOSYS 是常态,不是配置问题。
跨平台兼容性现实
Windows 没有等价于 CLOCK_THREAD_CPUTIME_ID 的轻量接口。GetThreadTimes 能拆分用户/内核时间,但开销显著更高(涉及内核对象查询),且返回的是 100ns 单位的 FILETIME,需手动转换;macOS 则只能通过 task_info + THREAD_BASIC_INFO 查整个进程,无法精确到线程。
这意味着:如果你的代码需要跨平台,要么接受“仅 Linux 支持线程级细粒度 CPU 时间”,要么退回到进程级统计(用 CLOCK_PROCESS_CPUTIME_ID 或 getrusage(RUSAGE_SELF, …)),并清楚意识到这会掩盖线程间调度差异。
真正难的不是读数据,而是理解「线程 CPU 时间」在 cgroup、CPU 频率缩放、SMT(超线程)下的语义偏移——这些场景下,两个数值相减得到的“耗时”可能和你预期的物理时间对不上。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











