You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GRPC场景选型:RPC返回google.protobuf.Empty时选异步还是同步客户端API

C++ gRPC客户端无返回值场景的API选择最佳实践

核心场景回顾

客户端响应用户事件发起RPC调用,所有调用返回google.protobuf.Empty,无需处理返回结果。


同步API的适用场景与性能表现

  • 代码复杂度极低:同步调用直接通过Stub::Method()发起,无需管理CompletionQueue和后台线程,逻辑直观,调试成本低。
  • 性能下限足够:gRPC同步API底层基于异步机制封装,单调用的延迟与异步API相差无几。在低并发场景(如每秒数十次调用以内),完全能满足需求。
  • 注意事项:如果发起调用的线程是不能阻塞的关键线程(如UI线程、实时业务线程),绝对不能直接用同步调用——哪怕返回Empty,网络请求的延迟(哪怕毫秒级)也会导致线程卡顿,影响用户体验或业务稳定性。这种情况下必须把同步调用放到独立的工作线程中执行。

异步API的优化与必要性

Google推荐异步API的核心原因是高并发下的资源利用率:异步模式可以复用少量线程处理大量RPC请求,避免同步模式下因线程阻塞导致的大量线程上下文切换开销。

针对你觉得“需要后台线程处理CompletionQueue冗余”的顾虑:

  • 后台线程的实际开销极小:这个线程绝大多数时间会阻塞在CompletionQueue::Next()调用上,仅当RPC完成时才会短暂唤醒,CPU占用可以忽略不计。
  • 简化处理逻辑:因为无需处理返回结果,后台线程拿到事件后直接丢弃即可,无需额外业务逻辑。示例代码如下:
    // 后台线程循环
    void CompletionQueueWorker(grpc::CompletionQueue* cq) {
      void* tag;
      bool ok;
      // 持续处理队列事件,直到队列被关闭
      while (cq->Next(&tag, &ok)) {
        // 无需处理结果,直接释放tag(如果分配了的话)
        delete static_cast<std::uintptr_t*>(tag);
      }
    }
    
    // 发起异步调用
    void TriggerRpc(AsyncStub* stub, grpc::CompletionQueue* cq, const Request& req) {
      grpc::ClientContext ctx;
      google::protobuf::Empty resp;
      // 分配一个简单的tag(仅用于标识调用)
      auto tag = new std::uintptr_t(0);
      stub->AsyncYourMethod(&ctx, req, cq)->Finish(&resp, tag);
    }
    

最佳实践总结

  1. 低并发+非关键线程发起调用:优先选同步API,代码简洁,无额外维护成本,性能完全够用。
  2. 高并发场景:必须用异步API,后台线程的开销远低于高并发下同步模式的线程资源浪费。
  3. 关键线程发起调用:无论并发高低,都要用异步API(或把同步调用放到工作线程),避免阻塞关键线程影响业务。

内容的提问来源于stack exchange,提问作者PluginPenguin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:32:55