news 2026/9/13 11:35:28

brpc 并发模型选型指南:同步、异步还是 bthread?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
brpc 并发模型选型指南:同步、异步还是 bthread?

brpc 并发模型选型指南:同步、异步还是 bthread?

【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc

这篇技术指南围绕 brpc 中最常见的架构选择题展开:当服务需要并发时,到底该用同步接口、异步接口,还是 M:N 线程库 bthread?文章从 brpc 官方文档 bthread_or_not.md 的核心决策框架出发,结合 client.md、combo_channel.md 与 src/bthread 源码实现,给出可直接套用的量化判断公式、可复制的代码示例,以及并行计算场景下 bthread 的正确打开方式。读完你将掌握一套不依赖直觉、可量化、可验证的并发模型选型方法论。

选型问题的本质:三种并发工具,各自解决什么问题

brpc 提供了 异步接口,又提供了 bthread,因此一个高频问题随之而来:我应该用异步接口还是 bthread?

要回答这个问题,首先要厘清三者各自的定位:

  • 同步接口:延续传统的"阻塞直到 RPC 返回"的编程模型,代码最简单、最容易理解,但一次调用会占住一个执行体;
  • 异步接口:通过回调(done)代替阻塞,CallMethod 发出 request 后立即返回,让出执行体以承载更高并发;
  • bthread:brpc 使用的 M:N 线程库,M 个 bthread 映射到 N 个 pthread(一般 M 远大于 N),可以在同步的编程模型下实现并行计算。

brpc 的官方结论非常直白:

延时不高时你应该先用简单易懂的同步接口,不行的话用异步接口,只有在需要多核并行计算时才用 bthread。

这句话是整篇选型指南的骨架,下面逐一展开其背后的判断依据。

同步还是异步:用 qps × latency 公式做量化判断

为什么不能盲目追随"全异步"风格

异步的本质是"用回调代替阻塞,有阻塞的地方就有回调"。javascript 这类语言中回调大行其道,接受度很高,但很多鼓吹异步的人写的代码"是从头到尾从下到上全异步且不考虑多线程的",和真实业务代码是两码事。关键差异在于:

  • javascript 是单线程的,回调之间没有竞争;
  • 而 brpc 的异步是多线程的,异步回调会运行在与调用处不同的线程中,你获得多核扩展性的代价,是必须时刻意识到多线程问题。

那能不能把服务组织成"多个线程、每个都是独立 eventloop"的形式呢?可以,但实际效果糟糕:当阻塞发生在循环、条件分支、深层子函数中时,改造成本极高,老代码和第三方代码往往根本没法改。结果就是代码中不可避免出现阻塞,导致那个线程中其他回调都被延迟,流量超时,server 性能反而不符合预期。

brpc 异步的真实面貌

brpc 中的异步和单线程异步完全不同:异步回调会运行在与调用处不同的线程中。这意味着:

  • 你会在回调中获得真正的多核扩展性;
  • 只要线程够用,你可以在回调中阻塞,对 server 整体性能并无大碍。

从 client.md 的实现说明可以看到,异步访问是给 CallMethod 传递一个额外的回调对象 done,CallMethod 发出 request 后即结束,当 server 返回 response 或发生错误(包括超时)时,done->Run() 才会被调用,后续处理都应写在 done->Run() 里。由于 CallMethod 结束不意味着 RPC 结束,response/controller 一般得创建在堆上并在 done->Run() 中删除。

不过异步代码终究难写,所以 brpc 提供了 组合访问:通过组合不同的 Channel,你可以声明式地执行复杂访问而不用关心细节。典型的如 ParallelChannel,同时访问其包含的 sub channel 并合并结果,支持同步和异步访问、发起异步操作后可立刻删除、可取消、支持超时,还可用fail_limit/success_limit控制提前结束条件。

判断公式:计算同时进行的平均请求数

brpc 给出的判断标准是一个可量化的公式:

判断使用同步或异步:计算qps * latency(in seconds),如果和 CPU 核数是同一数量级,就用同步,否则用异步。

文档给出了三个具体算例:

场景qpslatency计算结果 qps × latency结论
高 QPS 低延时200010ms2000 × 0.01s = 20与常见 32 核同数量级,用同步
低 QPS 高延时1005s100 × 5s = 500与核数不在同一数量级,用异步
中等场景500100ms500 × 0.1s = 50基本同数量级,可用同步;延时若继续增长再考虑异步

这个公式计算的是同时进行的平均请求数(可用 Little's Law 类似思路推导:在途请求数 = 到达率 × 平均服务时间)。它和线程数、CPU 核数是可比的:

  • 当这个值远大于 CPU 核数时,说明大部分操作并不耗费 CPU,而是大量线程在阻塞等待。此时使用异步可以明显节省线程资源(主要是栈占用的内存);
  • 当这个值小于或和核数差不多时,异步能节省的线程资源很有限,这时"简单易懂的同步代码"更重要。

异步还是 bthread:并发 RPC 别用 bthread,并行计算才用

有了 bthread 这个工具,用户甚至可以自己实现异步。文档以"半同步"为例,展示了两种实现路径:

  1. 发起多个异步 RPC 后挨个Join,该函数会阻塞直到 RPC 结束;
  2. 启动多个 bthread 各自执行同步 RPC,然后挨个 join bthreads。

哪种效率更高?显然是前者。后者不仅要付出创建 bthread 的代价,而且在 RPC 过程中 bthread 还被阻塞着,不能用于其他用途。

如果仅仅是为了并发 RPC,别用 bthread。

这条结论与 client.md 中"半同步"的实现思路一致:用Join实现"等待多个异步访问完成",因为调用处会等到所有 RPC 都结束后再醒来,controller 和 response 都可以放栈上。更重要的是,文档明确指出:实现中建议你使用 ParallelChannel,而不是自己 Join——ParallelChannel 把"并发发起 + 合并结果"封装成了声明式的 Channel 组合,避免手写多线程回调的各类陷阱(如生命周期、取消、再组合等问题,combo_channel.md 开篇有系统论述)。

并行计算才是 bthread 的主场:树形并行

当需要并行计算时,问题就完全不同了。使用 bthread 可以简单地构建树形的并行计算,充分利用多核资源。以检索过程为例,三个环节可以并行处理:建立两个 bthread 运行其中两个环节,在原地运行剩下的环节,最后 join 那两个 bthread:

bool search() { ... bthread th1, th2; if (bthread_start_background(&th1, nullptr, part1, part1_args) != 0) { LOG(ERROR) << "Fail to create bthread for part1"; return false; } if (bthread_start_background(&th2, nullptr, part2, part2_args) != 0) { LOG(ERROR) << "Fail to create bthread for part2"; return false; } part3(part3_args); bthread_join(th1); bthread_join(th2); return true; }

这个写法的两个关键 point:

  1. 节省一个线程资源:你当然可以建立三个 bthread 分别执行三个部分再 join 它们,但相比本方法要多耗费一个线程资源(bthread 也是要占栈、要调度的);
  2. 调度延时可控且可被掩盖:bthread 从建立到执行是有延时的(调度延时)。在不是很忙的机器上,这个延时的中位数在3 微秒左右,90% 在10 微秒内,99.99% 在30 微秒内。这给出两个实操推论:
    • 计算时间超过 1ms 时收益比较明显。如果计算非常简单,几微秒就结束了,用 bthread 没有意义;
    • 尽量让原地运行的部分最慢。这样 bthread 中的部分即使被延迟几微秒,最后可能还是会先结束,从而消除掉延迟的影响;并且bthread_join一个已结束的 bthread 时会立刻返回,不会有上下文切换开销。

源码级印证:API 行为与调度模型

上述代码用到的两个核心 API 定义在 src/bthread/bthread.h:

  • bthread_start_background(tid, attr, fn, args)(bthread.h#L67-L70):创建 bthread,行为接近pthread_create——把新线程放入调度队列后即返回,新线程可能比bthread_start_urgent()更晚运行。这正是"从建立到执行有调度延时"的接口层面原因;
  • bthread_join(bt, bthread_return)(bthread.h#L116-L124):等待 bthread 终止,若已终止则立即返回——这正是"join 已结束的 bthread 立刻返回、无上下文切换开销"的实现依据。注意*bthread_return始终被置为 null,如需回传结果,应通过创建时传入的 args 传递。

为什么 bthread 能支撑这种"原地等待并行结果"的模型?从 bthread.md 的 FAQ 与源码结构可以确认两点关键技术:

  • Work stealing 调度:pthread worker 在任何时间只运行一个 bthread;当前 bthread 挂起时,worker 先尝试从本地 runqueue 弹出待运行的 bthread,没有则随机偷其他 worker 的,仍没有才睡眠等待唤醒。这保证了被创建出来的 bthread 能快速被空闲核心调度;
  • butex(bthread mutex/condition 底层原语):让 bthread 和 pthread 可以相互等待和唤醒,实现 src/bthread/butex.cpp 等。

bthread 是 M:N 线程库,一个 bthread 被卡住不会影响其他 bthread;bthread 中也可以调用阻塞的 pthread 或系统函数,只会阻塞当前 pthread worker,其余 worker 不受影响。但要注意:若有大量 bthread 同时阻塞(比如 8 个 worker 全被 usleep 占住),处理网络收发的 RPC 代码会暂时无法运行,可以通过调大 worker 数缓解,server 端可设置ServerOptions.num_threads-bthread_concurrency(详见 server.md)。

类线程池需求:用 ExecutionQueue 替代

除了树形并行计算,当你有类似线程池的需求(比如执行一类 job 的线程池)时,也可以用 bthread 代替。如果对 job 的执行顺序有要求,brpc 提供了基于 bthread 的 ExecutionQueue:它承担"队列 + 有序执行"的角色,多个线程可以向队列提交任务,任务以确定的顺序被执行。

之所以推荐 ExecutionQueue 而非自己造 channel,bthread.md 的 FAQ 给出了设计层面的解释:channel 代表两点间的关系,而很多现实问题是多点的,用 channel 的自然方案是划分角色、互相发号施令,这会引入额外的上下文切换开销,且"做成任何事情都得等到被调用处被调度、处理、回复",再优化也有明显开销。ExecutionQueue 正好补上了这个"buffered channel 扮演队列和有序执行"的位置。

小结:一套完整的三步选型决策

综合 bthread_or_not.md 的完整脉络,可以沉淀为三步决策流程:

  1. 先算账再选同步/异步:计算qps × latency(in seconds),与 CPU 核数比较。同一数量级用同步(代码简单易懂优先),远大于核数用异步(节省被阻塞线程的栈内存);
  2. 并发 RPC 一律别上 bthread:要并发发起多个 RPC,优先用 ParallelChannel 这类组合 Channel,用声明式组合替代手写回调与 Join;
  3. 只有真正的多核并行计算才用 bthread:计算量超过 1ms、需要并行跑多个环节时,用bthread_start_background+ 原地执行 +bthread_join构建树形并行,并把最慢的环节留在原地执行以掩盖调度延时;有顺序要求的任务队列则交给 ExecutionQueue。

brpc 的设计哲学也印证了这一点:在 bthread.md 的 FAQ 中,官方明确建议"除非你需要在一次 RPC 过程中让一些代码并发运行,你不应该直接调用 bthread 函数,把这些留给 brpc 做更好"。选型不是追求最潮的技术,而是让每一份并发能力都落在它最能发挥价值的位置上。

【免费下载链接】brpcbrpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".项目地址: https://gitcode.com/GitHub_Trending/brpc/brpc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 11:30:12

DES算法在企业数据安全中的应用与实现

1. 项目概述 这个毕业设计项目选择了一个非常实用的方向——企业用户数据安全保护。在当前数字化办公环境下&#xff0c;企业每天都会产生大量敏感数据&#xff0c;包括客户信息、财务记录、内部文档等。如何确保这些数据在存储和传输过程中的安全性&#xff0c;是每个企业IT部…

作者头像 李华
网站建设 2026/9/13 11:26:57

光伏MPPT技术:Simulink仿真与算法优化实践

1. 光伏MPPT技术背景与核心挑战光伏系统在实际运行中面临的最大技术难题之一&#xff0c;就是如何确保在不同环境条件下都能从太阳能电池板提取最大功率。这个问题的根源在于光伏电池的非线性输出特性——其电流-电压&#xff08;I-U&#xff09;曲线会随着光照强度、温度以及阴…

作者头像 李华