runqlat / runqlen:线程等 CPU 等了多久
更新时间:2026-09-19。
load average和 CPU 利用率高,不等于「每个请求都在被及时调度」。当可运行线程多于核数,线程会在**运行队列(run queue)**排队,这段「想跑却没轮到上 CPU」的时间叫调度延迟,bcc 的 runqlat/runqlen 正是测它。
一、为什么运行队列延迟重要
线程被唤醒(有活干) ──[运行队列等待]──► 真正拿到 CPU 执行
◄── runqlat 量这段 ►- 这段时间线程不在 CPU 上、也不算 IO 等待,但请求延迟在白白增加。
- CPU 利用率 100%、run queue 持续排队时,扩容通常立刻降低尾延迟;这是容量规划要抓的硬信号。
- 与 off-CPU 区别:off-CPU 可能是主动等 IO/锁(合理),run queue 等待是就绪却抢不到 CPU(资源不足),性质不同。
二、runqlat:等待延迟直方图
sudo runqlat # 全系统调度等待延迟(微秒直方图),Ctrl+C 汇总
sudo runqlat -m # 毫秒
sudo runqlat -p 1234 # 只看某进程
sudo runqlat -P # 按 PID 分别输出
sudo runqlat 10 1 # 采样 10 秒一次 usecs : count distribution
2 -> 3 : 8421 |****************************************|
4 -> 7 : 1203 |***** |
16 -> 31 : 88 | |
128 -> 255 : 12 | | ← 排队尖刺健康系统大多数等待在几微秒(调度器正常切换);如果整体右移、出现大量毫秒级,说明线程在排队抢核。
三、runqlen:有多少线程在排队
sudo runqlen # 运行队列长度分布(含占用 CPU 的计数方式因版本而异)
sudo runqlen -C # 按每个 CPU 输出
sudo runqlen 5 2runqlen 回答「同一时刻有多少线程想跑」:持续 ≥2、3 个线程/核在等,就是 CPU 饱和。它与 runqlat 配合:runqlen 看队列深度(有没有排队),runqlat 看排队代价(等了多久)。
四、怎么和其它指标交叉判断
| 现象 | 工具 | 结论方向 |
|---|---|---|
| runqlen 常 > 核数、runqlat 右移 | mpstat 各核 %usr 高、vmstat r 列 大 | CPU 容量不足,扩容/减少线程 |
| runqlat 不高但请求慢、offcputime 大 | iostat await 高、锁等待 | IO/锁瓶颈,加 CPU 无用 |
| 单核队列长、其它核空闲 | mpstat 软中断不均、绑核/中断问题 | 调度/中断不均,调 RSS/亲和性 |
| 周期性 runqlat 尖刺 | GC、定时任务、cgroup throttling | 查周期性事件与 CPU quota |
排查 on-CPU 热点用 perf 火焰图,等 IO/锁用 offcputime/bpftrace,等 CPU 用 runqlat——三者覆盖「忙、等资源、等调度」三种慢。
五、容器环境注意
容器里看到的高 runqlat 可能来自 CFS bandwidth control(cpu.cfs_quota_us 节流):即使宿主机有空闲核,容器用满配额也会被限流、线程在配额窗口内排队。要同时看 cpu.stat 的 nr_throttled/throttled_time,别误判成机器整体容量不足,见 JVM 容器内存/资源限制。
坑
- 把所有 off-CPU 时间都当容量问题:等 IO/锁时线程不在 run queue,runqlat 不会高。
- 只看平均延迟,忽略毫秒级右尾——尾延迟才是用户卡顿来源。
- 忽略 CPU 亲和性:单队列堆积其它核空闲是调度/绑核问题不是总容量问题。
- 容器里只看宿主指标,漏掉 cgroup CPU 限流导致的排队。
总结
runqlat 统计线程从就绪到真正获得 CPU 的运行队列等待延迟分布,runqlen 统计同时等待运行的线程数,二者共同判断系统是否 CPU 饱和:队列持续长、延迟整体右移且各核利用率高说明需要扩容,而 runqlat 不高但 off-CPU 长则是 IO 或锁等待、加机器无效;单核堆积要查中断绑核,容器中还要结合 cfs quota 节流的 cpu.stat 排查,避免把限流误判为容量不足。