RUST中的读写锁模式

3
我有一个类似这样的场景:Rust Playground
  • 我有一个带有锁的结构体Container,该锁保护多个成员。我不想为每个成员使用RwLock。
  • 我想获取这个锁,然后调用另一个函数complex_mutation,该函数执行一些复杂逻辑并改变这个结构体。
  • 问题是锁的RAII守卫_guard获取了一个引用,然后调用另一个函数complex_mutation,其中传递了&mut self,导致了多个不可变+可变引用。
  • 我不能在创建另一个可变引用之前放弃守卫,因为这正是提供同步的地方。
  • 如果我可以将complex_mutation中的所有内容内联,那么这个问题就解决了,但对于真实世界的情况来说,这太丑陋了。

这是C++中常见的模式。在RUST中如何解决这个问题?


将守卫传递给 complex_mutation 吗? - undefined
过了防守有什么帮助呢?这个函数 complex_mutation 是一个已存在的辅助函数,我宁愿不修改它。 - undefined
嗯,看着代码,锁是完全多余的,因为你在两个函数中都使用了&mut self。请记住,&mut 引用是排他的,所以程序中的任何其他部分都无法访问 *self。只需移除锁即可。 - undefined
你的 RwLock<()> 只保护了 (),没有其他作用。你应该使用 RwLock<(u32, u32)>,这样它就包含了要保护的数据。这与 C++ 中的做法不同。 - undefined
1
@NitishUpreti 如果你有一个&mut,那么可以保证程序中没有其他地方可以读取或写入该引用指向的内容。像RwLock这样的类型提供了所谓的“内部可变性”,这是一种概念,允许在特定情况下从共享引用(&)转换为独占引用(&mut)。如果你拥有一个独占引用,就不需要内部可变性。因此,如果你使用&self,那么RwLock会很有用,但如果你使用&mut self,它就不会有用。 - undefined
显示剩余3条评论
1个回答

5
你似乎对一些事情感到困惑。让我试着澄清一下。
锁在Rust中用于保护锁内的数据。在这种情况下,你正在保护一个无意义的 `()`。
如果你使用 `&mut self`,那么锁对你没有帮助。锁是让你从共享引用 (`&`) 中获取独占引用 (`&mut`) 的众多机制之一。当你从共享引用开始时,能够写入值的能力被称为“内部可变性”,它由 `RwLock`、`Mutex`、`RefCell` 等实现。如果你已经有了独占引用,那么内部可变性就是多余的。
解决了这个问题后,你想要做的是将类型中的数据放入锁中。你可以使用元组来实现,但更好的方法是使用一个私有的“内部”类型,像这样:
struct Container {
    lock: RwLock<ContainerInner>,
}

struct ContainerInner {
    pub data1: u32,
    pub data2: u32,
}

现在,你想要使用&self
如果你有需要在多个位置调用的方法来操作ContainerInner中的数据,你可以简单地将它们放在ContainerInner上,像这样:
impl Container {
    pub fn update_date(&self, val1: u32, val2: u32) {
        self.lock.write().unwrap().complex_mutation(val1, val2);
    }
}

impl ContainerInner {
    fn complex_mutation(&mut self, val1: u32, val2: u32) {
        self.data1 = val1;
        self.data2 = val2;
    }
}

请注意,如果您有一个&mut self,那么实际上您不需要锁定;如果您有一个&mut到锁的内部值的话,RwLock::get_mut()允许您获取该值,而无需锁定,因为拥有对锁的&mut是一个静态保证,表明该值不是共享的。
如果这有助于您的概念理解,&&mut的工作方式有点像RwLockread()write() -- 您可以拥有多个&或一个单独的&mut,如果您有一个&mut,则不能同时拥有&。(这有些简化,但您可以理解这个概念。)与运行时锁不同,Rust编译器在编译时检查这些使用是否正确,这意味着不需要运行时检查或锁定。
因此,您可以添加一个类似以下的方法:
impl Container {
    pub fn update_date_mut(&mut self, val1: u32, val2: u32) {
        self.lock.get_mut().unwrap().complex_mutation(val1, val2);
    }
}

这与其他方法完全相同,只是使用了&mut self,这使我们能够在write()的位置上使用get_mut(),从而绕过锁定。除非编译器可以证明该值未被共享,否则编译器不会允许您调用此方法,因此如果编译器允许您使用它,则可以放心使用。否则,您需要使用另一种会进行锁定的方法。

非常感谢如此出色的解释!这真正使观点清晰明了:“与运行时锁不同,Rust编译器在编译时检查这些使用是否正确,这意味着不需要运行时检查或锁。” - undefined
1
@NitishUpreti 是的!我认为关键的要点就是这句话:“如果你已经有了独占的参考,内部可变性就是多余的。” - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接