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

如何在Tokio单线程异步程序中生成修改状态的异步任务?

单线程Tokio异步程序的无锁状态管理

我基于Tokio构建了一个单线程异步程序,其中状态(a、b)只会被同步更新,理论上修改状态时不需要使用锁。以下是我的思路代码,但无法编译:

struct Program {
    pub a: i32,
    pub b: i32,
}

impl Program {
    pub async fn run(mut self) {
        let local = tokio::task::LocalSet::new();
        local.spawn_local(async move {
            self.modify_a().await;
        });
        local.spawn_local(async move {
            self.modify_b().await;
        });
        local.await;
    }

    pub async fn modify_a(&mut self) {
        self.a = async_fn1().await;
    }

    pub async fn modify_b(&mut self) {
        self.b = async_fn2().await;
    }
}

我已经尝试过两种可行的解决方法:

  • 将整个函数体移入异步闭包
  • 将状态移至thread_local变量并用RefCell包裹,实现Self::modify_x()这类静态成员函数

但尝试以下方法时失败了:

  • 将状态作为Program的字段并用RefCell包裹,此时要求self的生命周期必须超过'static,尽管我已经在函数结束前调用了local.await

我想知道有没有更优雅便捷的方式实现这种单线程行为(不需要使用Mutex或Arc),比如能不能用RefCell直接包裹self?


可行方案1:Rc<RefCell<Program>>共享状态

因为是单线程LocalSet环境,不存在线程安全问题,用Rc实现单线程共享所有权,搭配RefCell做运行时可变借用检查,完全符合无锁需求,且不需要'static生命周期限制。

修改后的代码示例:

use std::cell::RefCell;
use std::rc::Rc;
use tokio::task::LocalSet;

struct Program {
    pub a: i32,
    pub b: i32,
}

impl Program {
    pub async fn run(self) {
        let program = Rc::new(RefCell::new(self));
        let local = LocalSet::new();

        // 克隆Rc传递给任务,仅复制引用计数
        let prog_clone = Rc::clone(&program);
        local.spawn_local(async move {
            prog_clone.borrow_mut().modify_a().await;
        });

        let prog_clone = Rc::clone(&program);
        local.spawn_local(async move {
            prog_clone.borrow_mut().modify_b().await;
        });

        local.await;
    }

    pub async fn modify_a(&mut self) {
        self.a = async_fn1().await;
    }

    pub async fn modify_b(&mut self) {
        self.b = async_fn2().await;
    }
}

// 示例异步函数
async fn async_fn1() -> i32 { 42 }
async fn async_fn2() -> i32 { 24 }

可行方案2:Tokio单线程优化版Mutex

虽然你不想用锁,但Tokio的Mutex在单线程环境下会自动退化为无锁实现(内部用RefCell),API更符合异步代码习惯,且无需手动处理Rc:

use tokio::sync::Mutex;
use tokio::task::LocalSet;

struct Program {
    pub a: i32,
    pub b: i32,
}

impl Program {
    pub async fn run(self) {
        let program = Mutex::new(self);
        let local = LocalSet::new();

        local.spawn_local(async {
            program.lock().await.modify_a().await;
        });

        local.spawn_local(async {
            program.lock().await.modify_b().await;
        });

        local.await;
    }

    pub async fn modify_a(&mut self) {
        self.a = async_fn1().await;
    }

    pub async fn modify_b(&mut self) {
        self.b = async_fn2().await;
    }
}

// 示例异步函数
async fn async_fn1() -> i32 { 42 }
async fn async_fn2() -> i32 { 24 }

这个方案的优势是代码更简洁,若未来需要扩展到多线程,无需大幅修改代码,Tokio会自动切换为线程安全的锁实现。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:45:28