线程模型选择
krpc 提供异步接口,但在多线程/多核环境下如何选择线程模型,需要结合 QPS、延迟、CPU 核心数 以及 任务特性 来判断。
1. 同步 vs 异步
-
核心原则:低延迟场景,先用简单易懂的同步接口;只有同步无法满足性能时,才使用异步接口。
-
异步的误区:
-
JavaScript 风格的单线程异步回调在多线程环境中无法直接使用。
-
转换阻塞同步代码到回调风格很难,尤其是:
-
循环内部阻塞
-
条件分支复杂
-
依赖第三方库
-
结果往往是“难以维护且性能不佳”。
-
krpc 异步不同于 JS 异步:
-
回调运行在不同线程
-
可阻塞回调(只要线程资源充足)
-
支持组合通道(Combo Access)简化复杂调用
选择规则:同步还是异步?
公式:QPS * latency (in seconds)
- 如果这个值 ≈ CPU 核心数 → 使用同步
- 如果这个值 >> CPU 核心数 → 使用异步
示例:
| QPS | 延迟 | 平均并发请求 | 推荐 |
|---|---|---|---|
| 2000 | 10ms | 20 | 同步 |
| 100 | 50ms | 500 | 异步 |
| 500 | 100ms | 50 | 同步可行 |
解释:
- 值远大于 CPU 核心数 → 大量线程阻塞 → 异步可节省线程/内存
- 值接近或小于 CPU 核心数 → 异步收益不大,保持同步代码简单可读
2. 异步 vs kthread
-
只需并发 RPC 调用 → 异步比 kthread 更高效
-
kthread 创建和阻塞 RPC 会增加额外开销
-
推荐用异步 +
ParallelChannel组合调用 -
需要多核并行计算 → kthread 合理
-
可构建树形并行计算
-
举例:三阶段可并行处理
kthread th1, th2;
kthread_start_background(&th1, NULL, part1, args1);
kthread_start_background(&th2, NULL, part2, args2);
part3(args3); // 当前线程处理最慢任务
kthread_join(th1);
kthread_join(th2);
- 经验要点:
- kthread 创建到执行延迟 ≈ 3–30µs
- 只有任务耗时 > 1ms,使用 kthread 才显著
- 尽量在当前线程执行最慢的任务,减少调度延迟影响
- 可用 ExecutionQueue 管理顺序执行任务(内置 kthread)
3. 小结:线程模型选择原则
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 低延迟、低 QPS | 同步接口 | 简单易读,线程资源足够 |
| 高并发阻塞 RPC | 异步接口 | 节省线程栈和资源 |
| CPU 密集型、多核并行 | kthread | 支持树形任务并行,充分利用 CPU |
| 任务需要顺序执行 | ExecutionQueue | 任务顺序严格,利用 kthread 简化同步 |
建议流程
- 先同步:简单、低延迟场景
- 遇到瓶颈 → 异步接口
- 需要并行计算 → kthread 或 ExecutionQueue
- 高密度依赖任务 → 考虑 Taskflow(复杂 DAG 并行)
简言之:同步优先,异步节约线程,kthread 用于 CPU 密集或并行计算,ExecutionQueue 用于保证任务顺序。