PAYATHON 2026

C++ SPSC 队列启用 SCHED_FIFO 后延迟翻倍,该如何排查?

数据小陈

仅凭这些数据,无法确认延迟翻倍是由 RT Throttling 导致的。建议先检查 pthread_setschedparam() 是否成功、两个线程是否绑定到同一个逻辑 CPU,以及延迟统计是否包含队列排队时间,再考虑 CPU isolation。

SCHED_FIFO 的优先级决定线程如何获得 CPU,并不会让队列操作本身执行得更快。对于持续忙等的 Producer 和 Consumer,调度策略甚至可能加剧线程之间的资源争用。

先确认 SCHED_FIFO 是否设置成功

原来的函数返回了 pthread_setschedparam() 的结果,但没有展示调用方如何处理。如果设置失败,就不能把后续结果归因于已生效的实时调度策略。

这个 API 成功时返回 0,失败时直接返回错误码。应检查返回值,不要直接用 errno 判断这次调用的错误:

#include <pthread.h>
#include <sched.h>
#include <cstdio>
#include <cstring>

inline int setThreadPriority() {
    sched_param param{};
    param.sched_priority = 70;

    const int rc = pthread_setschedparam(
        pthread_self(), SCHED_FIFO, &param);

    if (rc != 0) {
        std::fprintf(stderr,
                     "pthread_setschedparam: %s\n",
                     std::strerror(rc));
    }

    return rc;
}

Producer 和 Consumer 都应分别检查返回值,在正式计时前确认设置结果。如果失败原因是权限或资源限制,需要先解决运行环境的问题。

检查两个线程实际绑定到了哪里

优先确认两个线程是否绑定到了同一个逻辑 CPU。

在同一个逻辑 CPU 上,同优先级的 SCHED_FIFO 线程不会按普通时间片轮转。如果当前运行的线程一直忙等,不阻塞也不主动让出 CPU,另一个同优先级线程可能迟迟得不到运行机会。例如,Consumer 在空队列上持续自旋可能妨碍 Producer 运行;Producer 在满队列上持续重试,也可能产生类似问题。

先查看系统实际识别的拓扑:

lscpu
lscpu -e=CPU,CORE,SOCKET,NODE

再分别查询两个线程的实际设置,将下面的 <TID> 替换为对应线程 ID:

taskset -pc <TID>
chrt -p <TID>

做对照实验时,可以先将两个线程绑定到不同物理核心,避免一开始就使用同一物理核心的两个 SMT 逻辑 CPU。后者仍会共享部分执行资源。

原始环境中的 socket、core 和 CPU 型号描述也应以实际输出复核。1 NUMA node 表示系统报告一个 NUMA node,不能理解为零个节点,也不能据此排除其他缓存或内存访问开销。

明确测到的是哪一种延迟

目前缺少队列实现和计时代码,无法判断这组毫秒级数据具体包含哪些阶段。

如果每条消息在 Producer 中记录时间,到 Consumer 取出时再计算时间差,那么结果包含队列排队时间。Producer 持续快于 Consumer 时,即使单次队列操作很短,后面的消息仍可能等待数毫秒。

这份结果的最小值为 308ns,p50 约为 7.7ms。排队和运行时干扰都值得检查,但仅凭这些数值无法确定原因,也不能把最小值直接当作队列操作的固定成本。

建议补充记录:

  • 时间戳记录在写入前、成功入队后,还是成功出队后。
  • 队列容量、队列占用情况,以及写入失败后是否重试。
  • Consumer 的处理吞吐量,以及测试期间队列是否持续积压。
  • 使用的计时方式是否适合跨线程计算时间差。
  • 内存分配、日志输出和分位数计算是否进入了计时区间。

Failed write:0 只能说明这项计数为零,不能证明队列没有积压,还需要结合容量和计数逻辑解释。

可以分别测试固定速率发送、逐步增加发送速率,以及最大吞吐量下的延迟,以便判断延迟是否随着负载接近消费能力而上升。

用证据判断是否发生 RT Throttling

RT Throttling 是一个排查方向,但“切到实时调度后变慢”本身不能证明发生了限流。

可以先读取系统的实时调度带宽配置:

cat /proc/sys/kernel/sched_rt_period_us
cat /proc/sys/kernel/sched_rt_runtime_us

这些值反映相关的周期和运行时间预算配置;实际约束还可能受内核配置及任务所在调度组影响。看到有限预算,并不等于当前测试已经触发限流。

应结合调度跟踪观察:线程在延迟峰值期间是否仍可运行,却长时间没有获得 CPU;停顿是否与实时运行预算的耗尽和恢复相吻合。也可以检查内核日志是否出现相关提示,但没有提示不能证明没有发生限流。

不建议直接通过关闭 RT Throttling 验证性能。持续运行的 SCHED_FIFO 忙等线程可能挤压普通任务的运行机会,使系统操作和排查更加困难。

CPU isolation 应放在什么阶段

CPU isolation、IRQ affinity 和减少后台任务干扰是低延迟环境中常见的配置手段,效果取决于瓶颈。它们无法解决错误的 CPU 绑定,也无法消除 Producer 长期快于 Consumer 造成的排队。

建议按以下顺序做对照:

  1. 使用优化构建,预热测试,并移除计时区间内的日志、分配和统计计算。
  2. 在相同负载下,比较默认调度策略和 SCHED_FIFO,记录两者设置是否成功。
  3. 固定到不同物理核心,确认实际 affinity,并记录队列占用和吞吐量。
  4. 跟踪延迟峰值对应的调度行为,区分线程未获调度、队列积压和其他干扰。
  5. 如果证据表明中断或后台任务影响明显,再逐项尝试 CPU isolation、IRQ affinity 等配置。

如果延迟主要来自线程同时访问队列时的缓存争用,再检查读写索引是否共享 cache line、内存分配是否进入热路径,以及 atomic 的内存序是否符合算法要求。没有实现代码时,不能直接建议把所有操作改为 memory_order_relaxed,否则可能破坏正确性。

补充队列实现、计时位置、线程绑定代码,以及启用 SCHED_FIFO 前后的完整对照结果,有助于判断下一步该查队列、负载还是调度器。

备注:内容仅供参考。