RPC 系列(十六):何时该用 bthread

brpc 提供了异步接口,所以一个常见的问题是:我应该用异步接口还是 bthread?

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

为什么不首选回调式的异步

异步即用回调代替阻塞,有阻塞的地方就有回调。虽然在 JavaScript 这种语言中回调工作得很好,接受度也非常高,但只要用过,就会发现这和我们需要的回调是两码事。这个区别不是 lambda,也不是 future,而是 JavaScript 是单线程的:JavaScript 的回调放到多线程下可能没有一个能跑过,竞争太多,单线程的同步方法和多线程的同步方法是完全不同的。

那是不是服务能搞成类似的形式呢?多个线程,每个都是独立的 event-loop。可以,ubaserver 就是(注意带 a),但实际效果糟糕,因为阻塞改回调可不简单:当阻塞发生在循环、条件分支、深层子函数中时,改造特别困难,况且很多老代码、第三方代码根本不可能去改造。结果是代码中会出现不可避免的阻塞,导致那个线程中其他回调都被延迟,流量超时,server 性能不符合预期。如果你说,「我想把现在的同步代码改造为大量的回调,除了我其他人都看不太懂,并且性能可能更差了」,我猜大部分人不会同意。别被那些鼓吹异步的人迷惑了,他们写的是从头到尾从下到上全异步且不考虑多线程的代码,和你要写的完全是两码事。

brpc 中的异步和单线程的异步是完全不同的:异步回调会运行在与调用处不同的线程中,你会获得多核扩展性,但代价是你得意识到多线程问题。你可以在回调中阻塞,只要线程够用,对 server 整体的性能并不会有什么影响。不过异步代码还是很难写的,所以我们提供了组合访问来简化问题:通过组合不同的 channel,你可以声明式地执行复杂的访问,而不用太关心其中的细节。

同步还是异步:一个简单的公式

当然,延时不长、QPS 不高时,我们更建议使用同步接口,这也是创建 bthread 的动机:维持同步代码也能提升交互性能。

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

比如,这个公式计算的是同时进行的平均请求数(你可以尝试证明一下),和线程数、CPU 核数是可比的。当这个值远大于 CPU 核数时,说明大部分操作并不耗费 CPU,而是让大量线程阻塞着,使用异步可以明显节省线程资源(栈占用的内存)。当这个值小于或和 CPU 核数差不多时,异步能节省的线程资源就很有限了,这时候简单易懂的同步代码更重要。

用 bthread 自己实现「半同步」

有了 bthread 这个工具,用户甚至可以自己实现异步。以「半同步」为例,在 brpc 中用户有多种选择。哪种效率更高呢?显然是不额外创建 bthread 的那种:后者不仅要付出创建 bthread 的代价,在 RPC 过程中 bthread 还被阻塞着,不能用于其他用途。

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

什么时候该用 bthread:并行计算

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

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

另外,当你有类似线程池的需求时,像执行一类 job 的线程池时,也可以用 bthread 代替。如果对 job 的执行顺序有要求,你可以使用基于 bthread 的 ExecutionQueue。

系列导航


   转载规则


《RPC 系列(十六):何时该用 bthread》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
RPC 系列(十七):bthread_id 详解 RPC 系列(十七):bthread_id 详解
bthread_id 是一个特殊的同步结构,它可以互斥 RPC 过程中的不同环节,也可以在 O(1) 时间内找到 RPC 上下文(即 Controller)。注意,这里我们谈论的是 bthread_id_t,不是 bthread_t(bth
2023-12-26
下一篇 
RPC 系列(十五):bthread 是什么 RPC 系列(十五):bthread 是什么
bthread 是 brpc 使用的 M:N 线程库,目的是在提高程序的并发度的同时,降低编码难度,并在核数日益增多的 CPU 上提供更好的 scalability 和 cache locality。「M:N」是指 M 个 bthread
2023-12-12
  目录