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

Linux 下用 clock_gettime(CLOCK_THREAD_CPUTIME_ID, …) 获取线程 CPU 时间
这是最直接、开销最小的方式,能同时拿到当前线程在用户态和内核态的总运行时间(单位纳秒),不需要 root 权限,且精度通常优于 getrusage。
关键点:
-
CLOCK_THREAD_CPUTIME_ID是 POSIX 标准定义的时钟 ID,但仅在 Linux 和部分 Unix 系统支持;glibc 2.4+ 已完整实现 - 返回值是自线程启动以来的**总 CPU 时间**(即用户态 + 内核态之和),不是 wall-clock 时间
- 若需拆分用户态/内核态,
clock_gettime不行,得换方案
#include <time.h>
#include <stdio.h>
<p>struct timespec ts;
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) {
long long nanos = ts.tv_sec * 1000000000LL + ts.tv_nsec;
printf("thread CPU time: %lld ns\n", nanos);
}Linux 下读取 /proc/self/task/[tid]/stat 拆分用户态与内核态时间
Linux 内核在 /proc/[pid]/task/[tid]/stat 文件中以空格分隔的字段形式暴露线程级统计信息,其中第 14 字段(utime)是用户态 jiffies,第 15 字段(stime)是内核态 jiffies。这是目前唯一能原生分离二者的方法。
实操注意:
立即学习“C++免费学习笔记(深入)”;
- 需先用
syscall(SYS_gettid)获取当前线程的 tid(不是pthread_self()返回的 pthread_t) - jiffies 需转换为纳秒:乘以
sysconf(_SC_CLK_TCK)得到秒,再转纳秒;常见系统上_SC_CLK_TCK是 100,即 1 jiffy = 10 ms - 该文件每行只有一条记录,但字段数固定为 52(内核 5.15+),必须按空格切割并跳过括号内的 comm 字段(第 2 字段),否则索引会错位
- 频繁读取
/proc文件有 I/O 开销,不适用于微秒级高频采样
#include <unistd.h>
#include <sys/syscall.h>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
<p>long long get_thread_cpu_times(long long& utime_ns, long long& stime_ns) {
pid_t tid = syscall(SYS_gettid);
std::ifstream f("/proc/self/task/" + std::to_string(tid) + "/stat");
std::string line;
if (!std::getline(f, line)) return -1;</p><pre class="brush:php;toolbar:false;">std::istringstream iss(line);
std::vector<std::string> fields;
std::string field;
while (iss >> field) fields.push_back(field);
// 字段 14 = utime (index 13), 字段 15 = stime (index 14),注意字段 2 是括号包住的 comm
if (fields.size() < 15) return -1;
long utime_jiffies = std::stoll(fields[13]);
long stime_jiffies = std::stoll(fields[14]);
long clk_tck = sysconf(_SC_CLK_TCK); // usually 100
utime_ns = utime_jiffies * (1000000000LL / clk_tck);
stime_ns = stime_jiffies * (1000000000LL / clk_tck);
return utime_ns + stime_ns;}
getrusage(RUSAGE_THREAD, …) 的兼容性陷阱
POSIX 提供 getrusage 并支持 RUSAGE_THREAD 参数来获取当前线程资源使用情况,理论上可读 ru_utime 和 ru_stime。但它在 glibc 中长期未真正实现线程粒度——直到 glibc 2.27(2018 年)才在 Linux 上通过读取 /proc/self/task/[tid]/stat 回退实现。这意味着:
- glibc getrusage(RUSAGE_THREAD, ...) 实际返回的是**整个进程**的累计值,不是当前线程
- 即便新版 glibc,
ru_maxrss、ru_nsignals等字段仍只对进程有效,不可信 - 跨平台无保障:macOS、FreeBSD 不支持
RUSAGE_THREAD,编译会失败或静默降级
所以除非你明确锁定较新 glibc 且只跑 Linux,否则别依赖 getrusage(RUSAGE_THREAD) 做线程级 CPU 时间拆分。
为什么不能用 clock_gettime(CLOCK_PROCESS_CPUTIME_ID)?
因为它是进程级的:返回整个进程所有线程的用户态 + 内核态 CPU 时间总和,无法区分当前线程贡献了多少。在多线程程序中,它会持续增长,哪怕当前线程处于 sleep 或 wait 状态——只要其他线程在跑,这个值就动。
典型误用场景:
- 想监控某 worker 线程的 CPU 消耗,却用了
CLOCK_PROCESS_CPUTIME_ID,结果看到数值飙升,误判为该线程忙,实际是别的线程在 crunch data - 做线程池负载均衡时拿这个值做调度依据,会导致严重偏差
真要进程级总时间,CLOCK_PROCESS_CPUTIME_ID 没问题;但线程级隔离,必须用 CLOCK_THREAD_CPUTIME_ID 或手动读 /proc/self/task/[tid]/stat。
真正麻烦的是字段索引和 jiffies 转换——/proc/stat 的字段顺序看似稳定,但内核文档明确说“字段可能随版本增减”,靠硬编码下标 13/14 本质是和内核签了隐式契约;而 sysconf(_SC_CLK_TCK) 在某些实时系统上可能不是 100。这些细节不踩一次坑很难记住。


















