Rust使用tonic无法并行发起多请求的原因及实现方法
报错原因解析
你的判断完全正确。tonic根据proto定义生成的say_hello方法要求传入&mut self作为参数,方法返回的Future会绑定当前可变引用的生命周期:只要这个Future没有被完成、没有被释放,就会持续持有client的可变借用。
你在第一次循环里调用client.say_hello生成的Future被存入responses列表,没有在本轮循环释放,后续迭代要再次获取client的可变引用时,就会触发借用检查规则,抛出E0499编译错误。
批量发起并行请求的实现方式
要实现“先收集所有请求的Future、再统一等待执行结果”的逻辑,完全不需要修改底层服务代码,直接利用tonic客户端本身的廉价克隆特性即可:
- tonic设计的
GreeterClient实例是轻量值,克隆操作不会新建TCP连接,只是共享底层的连接池通道,几乎没有额外性能开销。 - 每次调用请求方法前,先从基础客户端实例克隆出独立的客户端副本,让
say_hello返回的Future只绑定本轮循环的临时副本的可变引用,就不会出现跨迭代的可变借用冲突。
修改后的可正常编译运行的客户端代码如下:
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let num_requests = 10; // 基础客户端实例不需要标记mut,仅作为克隆的模板 let client = GreeterClient::connect("http://[::1]:50051").await?; let mut responses = Vec::with_capacity(num_requests); for _ in 0..num_requests { // 每次循环生成独立的可变客户端实例 let mut client = client.clone(); let request = tonic::Request::new(HelloRequest { name: "Tonic".into(), }); // 生成的Future仅绑定本轮临时实例的可变引用, // 不会占用全局基础实例的可变权限 responses.push(client.say_hello(request)); } // 统一等待所有请求结果,由tokio runtime自动调度实现并行执行 for resp in responses { assert!(resp.await.is_ok()); } Ok(()) }
如果想简化批量等待的逻辑,可以搭配异步生态的工具函数,将所有Future收集后一次性调度,不需要手动写遍历循环:
use futures::future::join_all; // 前面生成responses列表的逻辑保持不变 join_all(responses).await .into_iter() .for_each(|resp| assert!(resp.is_ok()));
内容的提问来源于stack exchange,提问作者matanmarkind
相关产品推荐
相关产品推荐

