pthread亲和性继承的一个坑:main绑核让整个进程退化到单核
现象C 多线程进程 qfactor19 万行/分钟的高频股票因子计算配 work_thread_nums8应该用 8 个build 线程并行处理 8 个 partition 的数据。但实测 CPU 只跑满 1 个核101%per-factor cycle耗时 23 秒同一份代码在另一个分支上 CPU 用满 8.3 核832%cycle 只要 2 秒。12倍速度差但代码逻辑、编译选项、ylfeature 子模块全部完全相同。排查过程按嫌疑度走过的死胡同根因// apps/qfactor/main.cc:147quick 上有if (config-getOtherCpuID() 0) {utility::bindCurrentThreadToCpu(config-getOtherCpuID(), main);} else {utility::bindCurrentThreadToCpu(0, main); // ← 默认 fallback 到 CPU 0}m3 分支没这段。Linux pthread_create 默认继承父线程的 CPU 亲和性man pthread_create。一旦 main 被pthread_setaffinity_np 绑到单核所有从 main 派生的子线程出生时都自动只能跑那个核- ✅ 显式 bindThreadToCpu 重绑的buildThreads[i]、sendThread、checkMasterThread——pthread_create后立刻被重绑到 config 指定核如果 config 有可以救回- ❌ 没有显式重绑机制的librdkafka 的 rdk:main / rdk:bro / 每个 broker 的 worker、ZMQ context内部 epoll 线程、Boost.Log async sink 后台线程、Redis hiredis subscriber、OceanView心跳——全部继承 main 的单核亲和性再也回不来如果 config 没配 other_cpu_id很多场景默认不配fallback 把 main 绑到 CPU0整个进程的所有线程被锁在 CPU 0 上 time-slice 共享。12 个线程挤一核每个线程拿到 ~7%CPU总和 100%。验证数据修 main.cc把那 5 行删掉重编译重跑CPU build 线程 cycle 耗时quick 修复前 101% 7-13% × 12 23.4squick 修复后 832% 87-99% × 11 2.0s ← 12× 提速m3 对照 893% 99-100% × 11 2.0s每只线程的 affinity mask- 修复前0x1仅 CPU 0- 修复后0xffffffff...所有核直接 taskset -p $tid 就能看出来。教训1. pthread_setaffinity_np 是有传染性的——绑了父线程后续 spawn出来的所有线程都被传染包括你看不见的第三方库内部线程。2. 如果一定要绑 main要么在所有子线程创建之后再绑要么用 pthread_attr_setaffinity_np给每个具体线程显式设亲和性。前者有种顺序依赖、后者要求你能控制每个线程的创建——第三方库做不到。3. 绑核默认值不要用 0。CPU 0 是最容易被系统中断IRQ 处理、softirq、内核 worker打扰的核。找不到配置就绑 0 是双重坑第一坑是上面的传染性第二坑是绑了一个最忙的核。4. 观测手段top -H 看每个线程的 CPU% 和 R/D/S 状态加上 taskset -p 查 affinitymask是最快定位类似问题的组合。top 总 CPU 看着只有 100%、但有 12 个线程都活着——这种线程多但CPU 上不去的反直觉模式就是亲和性继承在作祟。