当前位置: 首页 > news >正文

pthread亲和性继承的一个坑:main绑核让整个进程退化到单核

现象

C++ 多线程进程 qfactor(19 万行/分钟的高频股票因子计算),配 work_thread_nums=8,应该用 8 个
build 线程并行处理 8 个 partition 的数据。但实测 CPU 只跑满 1 个核(101%),per-factor cycle
耗时 23 秒;同一份代码在另一个分支上 CPU 用满 8.3 核(832%),cycle 只要 2 秒。12倍速度差,但代码逻辑、编译选项、ylfeature 子模块全部完全相同。

排查过程

按嫌疑度走过的死胡同:


根因

// apps/qfactor/main.cc:147(quick 上有)
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.4s
quick 修复后 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 上不去"的反直觉模式就是亲和性继承在作祟。

http://www.jsqmd.com/news/733186/

相关文章:

  • 终极指南:如何免费解锁Cursor Pro完整功能 - 技术解密与完整配置方案
  • Spring框架03(上):Spring 框架开发程序的方式:从零搭建一个原生 JDBC + Druid 的 Spring 项目(纯配置文件形式)
  • 关于华夏百川中频激光治疗仪相关负面报道的正式说明 - 野榜精选
  • 不只是看源码:用JD-GUI插件在IDEA里直接反编译依赖jar包
  • [开源] OpenTalking:整合 LLM、流式 TTS 与 WebRTC 的实时数字人编排框架
  • 保姆级教程:在YOLOv8中手把手替换BiFPN,并添加P2层提升小目标检测效果
  • 比亚迪DiLink 4.0车机Root实战:从固件提取到Magisk修补的保姆级避坑指南
  • 告别‘一病一药’:用PromptIR这个‘万能提示’模块,一个模型搞定图片去雾、去雨、去噪
  • 别再只用CBC了!聊聊OpenSSL AES ECB模式那些容易被忽略的坑(附C++实战代码)
  • 从Slack反推设计瓶颈:一个真实案例带你玩转Vivado Path Report
  • 保姆级教程:手把手教你将YOLOv8-Seg模型从PyTorch移植到C++推理引擎(附完整代码)
  • 从一次Samba挂载失败,聊聊Linux网络文件系统(CIFS/SMB)的版本兼容性与安全策略
  • 有效睡眠的本质的庖丁解牛
  • 从图像滤镜到推荐算法:Hadamard积和Kronecker积在AI项目里的‘隐藏’用法与性能调优
  • TVBoxOSC:打造你的全能电视盒子播放器终极指南
  • 2026年3月优秀的打包机企业口碑推荐,全自动打包机/手提式电动打包机/缠绕膜/彩色缠绕膜,打包机制造商有哪些 - 品牌推荐师
  • 麒麟系统桌面文件误删:数安寻搭建数据“重生”桥梁
  • 利用Taotoken模型广场为不同文本处理任务选择合适的模型
  • 3分钟学会:用stl-thumb为STL文件生成精美缩略图
  • 《OpenClaw本地知识库优化:从导入到优先调用指南》
  • 别再空谈4R了!用Notion或飞书搭建你的第一个客户关系管理看板(附模板)
  • Translumo:打破语言壁垒的智能屏幕翻译神器
  • 【学习笔记】Grader交互
  • 终极指南:如何用TMSpeech实现Windows本地实时语音转文字
  • Dify 2026多模态集成权威拆解:基于23家头部客户POC数据的延迟/吞吐/准确率三维基准测试报告(含可复现benchmark脚本)
  • 别再到处找Modbus主机库了!一个头文件搞定STM32CubeMX下的RTU主站通信
  • 微信同款存储引擎MMKV实战:从mmap原理到Protobuf编码,一次搞懂高性能背后的秘密
  • 告别弹窗卡顿!Android BottomSheetBehavior 性能优化与避坑实战(附完整代码)
  • 长期使用Taotoken服务感受到的API调用稳定性与技术支持响应
  • 告别激活烦恼:KMS_VL_ALL_AIO如何用一行命令解决Windows和Office激活难题