使用内存仓储模式时,Axum共享状态是否需额外Arc包装?
问题解答
核心结论
不需要给SharedState中的app_state字段额外添加Arc或Arc<Mutex>,仓储内部的Arc<RwLock<IndexMap<Uuid, Project>>>已经足够满足并发需求,也不会导致死锁。同时不要移除仓储中的Arc<RwLock>——这部分设计是合理的,能保证仓储crate在其他场景下的复用性。
具体分析
1. Axum共享状态的要求
Axum的共享状态只需要满足Clone + Send + Sync。因为你的ProjectRepository已经用Arc包装了RwLock,而Arc本身是可克隆(轻量的引用计数递增)、线程安全的,只要给ProjectRepository和包含它的AppState派生Clone trait,就可以直接作为Axum的共享状态使用,完全不需要额外的锁或引用包装。
2. 为什么不会死锁?
死锁的触发条件是循环持有锁且请求锁的顺序相反,或者持有锁时阻塞等待另一个锁。当前设计中:
- 只有仓储内部一层
RwLock负责保护IndexMap的并发访问; - Axum传递状态时只是克隆
Arc(无锁操作),不会引入第二层锁。
不存在多层锁的嵌套等待,因此不会产生死锁风险。
3. 保留仓储Arc<RwLock>的必要性
你希望仓储crate能在其他应用中复用,这个设计是正确的:
- 其他场景(比如CLI工具、非Axum的Web服务)可能不需要依赖Axum的状态模式,直接使用仓储自带的
Arc<RwLock>就能实现安全的并发访问; - 如果把并发控制移到
SharedState层,仓储就失去了独立的线程安全能力,复用性会大打折扣。
正确代码示例
仓储定义
use std::sync::{Arc, RwLock}; use indexmap::IndexMap; use uuid::Uuid; #[derive(Clone, Debug)] pub struct Project { // 项目字段示例 pub id: Uuid, pub name: String, } #[derive(Clone)] pub struct ProjectRepository { db: Arc<RwLock<IndexMap<Uuid, Project>>>, } impl ProjectRepository { pub fn new() -> Self { Self { db: Arc::new(RwLock::new(IndexMap::new())), } } // 示例方法:读取项目 pub fn get(&self, id: &Uuid) -> Option<Project> { let db_guard = self.db.read().expect("RwLock poisoned"); db_guard.get(id).cloned() } // 示例方法:插入项目 pub fn insert(&self, project: Project) -> Option<Project> { let mut db_guard = self.db.write().expect("RwLock poisoned"); db_guard.insert(project.id, project) } }
Axum状态与路由
use axum::{Router, routing::get, extract::State, http::StatusCode}; use uuid::Uuid; #[derive(Clone)] pub struct AppState { pub project_repo: ProjectRepository, } // 直接用AppState作为Axum的共享状态类型 type SharedState = AppState; async fn get_project( State(state): State<SharedState>, axum::extract::Path(project_id): axum::extract::Path<Uuid>, ) -> Result<Project, StatusCode> { state.project_repo.get(&project_id) .ok_or(StatusCode::NOT_FOUND) } #[tokio::main] async fn main() { let app_state = AppState { project_repo: ProjectRepository::new(), }; let router = Router::new() .route("/projects/:project_id", get(get_project)) .with_state(app_state); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(router.into_make_service()) .await .unwrap(); }
错误做法警示
不要给SharedState添加额外的Arc<Mutex>,比如:
// 错误:完全不必要,会增加锁开销,甚至可能引入死锁风险 type SharedState = Arc<Mutex<AppState>>;
这种写法会导致两层锁嵌套:每次请求需要先获取Mutex锁,再获取仓储内部的RwLock锁。如果有其他代码路径颠倒了锁的获取顺序,就可能触发死锁,同时完全浪费了仓储自带的并发控制能力。
内容的提问来源于stack exchange,提问作者A Bit of Help
相关产品推荐
相关产品推荐

