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

如何在Actix Web中实现可动态扩容的并发安全AppState?

问题描述

使用actix-web开发Web服务,基于web::Data实现AppState,需要通过API修改AppState的同时保证元素的并发处理能力。当前将AppState设计为存储Mutex<u64>计数器的向量,核心需求是通过API动态扩容向量(添加新计数器)。

最初尝试用Mutex包裹整个AppState(web::Data::new(Mutex::new(...))),虽然能编译,但会导致全局锁:修改向量中索引0的元素时,无法同时访问索引1的元素,丢失了并发处理能力。需要设计既能动态扩容向量,又能让每个元素独立并发处理的方案。

解决方案

核心思路是用**读写锁(tokio::sync::RwLock)**替代包裹整个AppState的Mutex,利用其「读操作共享、写操作独占」的特性实现需求:

  • 读操作(获取/修改单个计数器):使用读锁,允许多个请求同时执行,各自操作不同的计数器,互不阻塞;
  • 写操作(扩容向量添加新计数器):使用写锁,独占资源保证向量结构修改的安全性。

修改后的完整代码

use serde::Deserialize;
use tokio::sync::{Mutex, RwLock};
use tokio::time::{sleep, Duration};
use actix_web::{HttpServer, HttpResponse, App, get, post, web};

struct AppState(Vec<Mutex<u64>>);

#[derive(Deserialize)]
struct Query {
    ix: usize,
}

#[post("/push")]
async fn push_view(appdata: web::Data<RwLock<AppState>>) -> std::io::Result<HttpResponse> {
    // 写锁:独占资源,确保向量扩容的原子性
    appdata.write().await.0.push(Mutex::new(0));
    Ok(HttpResponse::Ok().finish())
}

#[get("/get")]
async fn get_view(appdata: web::Data<RwLock<AppState>>, query: web::Query<Query>) -> std::io::Result<HttpResponse> {
    // 读锁:共享资源,允许并发读取不同计数器
    let appstate = appdata.read().await;
    let val = appstate.0[query.ix].lock().await.clone();
    Ok(HttpResponse::Ok().body(val.to_string()))
}

#[post("/inc")]
async fn inc_view(appdata: web::Data<RwLock<AppState>>, query: web::Query<Query>) -> std::io::Result<HttpResponse> {
    // 读锁:共享资源,允许并发修改不同计数器
    let appstate = appdata.read().await;
    let mut val = appstate.0[query.ix].lock().await;
    sleep(Duration::from_secs(5)).await;  // 模拟长耗时操作
    *val += 1;
    Ok(HttpResponse::Ok().body(val.to_string()))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let appdata = web::Data::new(
        RwLock::new(AppState(vec![Mutex::new(0), Mutex::new(0)]))
    );

    HttpServer::new(move || {
        App::new()
            .app_data(appdata.clone())
            .service(push_view)
            .service(get_view)
            .service(inc_view)
    })
        .bind(("localhost", 8080))?
        .run().await
}

代码说明

  1. 全局锁替换:将包裹AppState的Mutex改为RwLock,通过读写分离提升并发能力;
  2. 扩容接口(/push):使用write().await获取写锁,此时所有读操作会等待,避免并发修改向量结构导致的安全问题;
  3. 计数器操作接口(/get、/inc):使用read().await获取读锁,多个请求可同时持有读锁,各自操作独立的Mutex<u64>计数器,实现真正的并发处理;
  4. 单个计数器安全:每个计数器仍用Mutex<u64>包裹,保证单个计数器的修改是原子性的,避免竞态条件。

Cargo.toml 无需修改

原依赖已包含tokio的full特性,RwLock属于tokio::sync模块,无需额外添加依赖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:37:03