我在写一些东京的异步码.我有一个多索引数据 struct ,将用户放在我想用粗粒度锁保护他们的位置(与每个对象锁相反).我有这样的东西:

use tokio::sync::RwLock;

struct User {
  id: u64,
  name: String,
}

// This class is not thread-safe.
struct UserDb {
  by_id: HashMap<u64, Arc<RefCell<User>>>,
  by_name: HashMap<String, Arc<RefCell<User>>>,
}

impl UserDb {
  pub fn add_user(&mut self, name: String) -> Result<(), Error> {
    // ...
  }
}

// This class is thread-safe.
struct AsyncDb {
  users: RwLock<UserDb>,
}

impl AsyncDb {
  pub async fn add_user(&self, name: String) -> Result<(), Error> {
    self.users.write().await.add_user(name)
  }
}

// QUESTION: Are these safe?
unsafe impl Send for AsyncDb {}
unsafe impl Sync for AsyncDb {}

最后没有SendSync特征,编译器就会抱怨RefCell<User>不是SendSync(合理地说是这样),因此通过AsyncDb::add_user访问/修改是不安全的.

我的解决方案是为数据 struct 实现SendSync,因为在UserDb附近的AsyncDb中有一个粗粒度的锁,其中包含上述RefCell.

这是一个正确的解决方案吗?它是否违反了任何不变量?有没有更好的办法来处理这件事?

注:这里是铁 rust 初学者.我可能有很多概念上的差距,所以如果事情没有意义,请说出来.

推荐答案

这几乎肯定是not%的声音,除非您only使用写锁定,即使在从用户读取时也是如此.

RefCell::borrow*()个函数不是线程安全的,因为它们不自动维护内部引用计数.这意味着当仅由读锁保护是不健全的时,使用borrow()RefCell<User>读取.

如果你已经喜欢这种特殊的设计,我强烈建议你用Mutex取代你的RwLock,因为读锁几乎是完全没有用的.

Mutex替换RwLock将使您的类型自动实现Sync,但它仍然不会实现Send.如果你把你的unsafe impl Send留在这种情况下,那么你需要非常小心地对待Arc.您绝不能:

  • 直接或嵌入到另一个值中返回Arc<RefCell<User>>或对1的引用,因为这将允许调用者克隆他们自己的Arc,他们可以使用这些Arc来操作RefCell,而不需要持有锁.
  • 通过thread::spawn()tokio::spawn()tokio::spawn_blocking()Arc<RefCell<User>>或对一个线程/任务的引用发送到另一个线程/任务,在通道上发送一个,依此类推.

Rust相关问答推荐

捕获Rust因C++异常而产生panic

如何将元素添加到向量并返回对该元素的引用?

抽象RUST中的可变/不可变引用

无法从流中读取Redis请求

如果包名称与bin名称相同,并且main.ars位于工作区的同一 crate 中,则无法添加对lib.ars的依赖

JSON5中的变量类型(serde)

为什么 GAT、生命周期和异步的这种组合需要 `T: 'static`?

如何将带有嵌套borrow /NLL 的 Rust 代码提取到函数中

可选包装枚举的反序列化

面临意外的未对齐指针取消引用:地址必须是 0x8 的倍数,但为 0x__错误

Rust中的标记特征是什么?

我如何取消转义,在 Rust 中多次转义的字符串?

不安全块不返回预期值

为什么可以在迭代器引用上调用 into_iter?

Rustlings 切片原语

在单独的线程上运行 actix web 服务器

如何将 u8 切片复制到 u32 切片中?

为什么 Rust 标准库同时为 Thing 和 &Thing 实现特征?

list 中没有指定目标 - 必须存在 src/lib.rs、src/main.rs、[lib] 部分或 [[bin]] 部分

在 macro_rules 中转义 $ 美元符号