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

Tonic服务处理首次批量阻塞RPC请求时并行度异常降低原因排查

Tonic服务处理首次批量阻塞RPC请求时并行度异常降低原因排查

问题场景重现

你基于Tonic的gRPC HelloWorld教程做了两处针对性修改:

  • 在say_hello方法返回响应前,加入std::thread::sleep(std::time::Duration::from_millis(10000)),故意用标准库的阻塞sleep而非Tokio的异步sleep,以此模拟真实场景中的CPU/IO阻塞型工作负载
  • 将Tokio运行时配置为多线程模式,先后指定过10、100个worker线程:#[tokio::main(flavor = "multi_thread", worker_threads = 100)]

测试时用批量启动客户端请求的方式验证并行能力:

for i in $(seq 1 20); do cargo run --quiet --bin helloworld-client& done

预期vs实际行为

  • 预期:按照配置的worker线程数,前10秒应该同时处理10个请求,接下来10秒处理剩余10个,总耗时约20秒
  • 实际:首次批量请求时,仅有1-3个请求被同时处理,后续批次才能达到满并行度,总耗时约30秒;更奇怪的是,即使服务器已经运行过,只要闲置一小段时间后再发批量请求,这个低并行度的问题依然会重复出现

核心原因分析

这个问题的根源在于Tokio多线程调度器的线程唤醒策略,再加上你使用阻塞型代码干扰了调度信号:

  1. Tokio worker线程的休眠(parking)机制:当worker线程没有任务可执行时,会进入parked(休眠)状态,等待新任务到来时被唤醒。但Tokio不会一次性唤醒所有闲置的worker线程,而是采用逐步唤醒的策略——一开始只唤醒少量线程处理请求,只有当检测到当前活跃线程都处于繁忙状态时,才会继续唤醒更多闲置线程。
  2. 阻塞代码干扰调度感知:你用的std::thread::sleep会直接阻塞整个worker线程的执行,而Tokio的调度器无法感知到这个线程内部的阻塞(因为这不是Tokio自身的异步休眠逻辑)。当第一批请求进来时,Tokio先唤醒少量worker线程处理请求,这些线程被阻塞后,调度器需要一段时间才能检测到它们真的“忙”,才会继续唤醒更多闲置线程——而这段检测窗口正好错过了第一批请求的并行处理时机,导致只有少数请求同时执行。

你的观察验证

你通过top -H -p <pid>确认worker线程确实都已创建,只是处于parked状态;用gdb调试看到闲置worker线程的调用栈停在tokio::runtime::scheduler::multi_thread::park::Inner::park相关逻辑,这正好验证了线程被休眠等待唤醒的状态,和我们的原因分析完全吻合。

解决方案建议

针对这种阻塞型工作负载,Tokio官方推荐的正确做法是:
使用tokio::task::spawn_blocking将阻塞代码放到专门的阻塞线程池中执行,而不是直接在worker线程里运行阻塞逻辑。这样worker线程可以快速释放,回到调度池处理其他请求,而阻塞任务由专门的线程处理,不会干扰Tokio的调度逻辑。

修改你的say_hello方法示例:

async fn say_hello(
    &self,
    request: Request<HelloRequest>,
) -> Result<Response<HelloReply>, Status> {
    println!("Got a request: {:?}", request);
    let name = request.into_inner().name;
    
    // 将阻塞逻辑放到spawn_blocking中,交给专门的阻塞线程池处理
    let reply = tokio::task::spawn_blocking(move || {
        std::thread::sleep(std::time::Duration::from_millis(10000));
        HelloReply {
            message: format!("Hello {}!", name),
        }
    }).await.unwrap();

    Ok(Response::new(reply))
}

这样修改后,worker线程不会被阻塞,Tokio调度器可以正常唤醒足够多的线程处理批量请求,并行度就能达到你配置的worker线程数。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:50:26