Rust单线程异步场景下如何避免使用锁处理共享可变引用
解决方案
Rust 的可变引用独占规则是编译期静态约束,不会根据运行时是否为单线程环境做特殊适配,因此即使逻辑上不存在并发修改风险,编译器也会拒绝同时存在多个可变引用的写法。针对单线程Tokio场景,你可以完全不用锁,通过以下两种方式实现需求:
方案1:使用RefCell实现单线程内部可变性(性能最优)
std::cell::RefCell是单线程场景下的内部可变性容器,仅在运行时检查借用规则,没有线程同步的开销,完全适配你的场景:
use tokio::join; use std::cell::RefCell; struct Time { hour: u8, minute: u8 } impl Time { pub fn set_time(&mut self, hour: u8, minute: u8) { self.hour = hour; self.minute = minute; } } fn main() { let mut runtime_builder = tokio::runtime::Builder::new_current_thread(); runtime_builder.enable_time(); let runtime = runtime_builder.build().unwrap(); runtime.block_on(async_main()); } async fn async_main() { // 用RefCell包裹Time,获得内部可变性 let time = RefCell::new(Time {hour: 0, minute: 0}); join!( task_1(&time), task_2(&time) ); } async fn task_1(time: &RefCell<Time>) { loop { // 临时获取可变借用,调用结束后借用立刻释放,不会跨await点 time.borrow_mut().set_time(10, 20); tokio::task::yield_now().await; } } async fn task_2(time: &RefCell<Time>) { loop { time.borrow_mut().set_time(12, 30); tokio::task::yield_now().await; } }
注意事项
只要保证RefCell的可变借用不会跨await点持有,就不会出现运行时借用冲突,你的set_time方法是无等待的同步逻辑,完全符合这个要求,不会出现panic。
方案2:消息传递(无运行时检查开销)
如果不想依赖运行时借用检查,可以通过通道将修改请求发送给持有Time所有权的父任务,由父任务统一修改状态,完全规避共享可变状态:
- 定义消息枚举表示修改请求
- 两个子任务通过发送端发送修改请求
- 父任务接收请求并更新
Time状态
这种方式完全在编译期保证安全,适合逻辑更复杂的单线程异步场景。
内容的提问来源于stack exchange,提问作者Josu Goñi
相关产品推荐
相关产品推荐

