使用它的守卫时如何避免互斥体借用问题

How to avoid mutex borrowing problems when using it's guard

我希望我的结构方法以同步方式执行。我想通过使用 Mutex (Playground):

来做到这一点
use std::sync::Mutex;
use std::collections::BTreeMap;

pub struct A {
    map: BTreeMap<String, String>,
    mutex: Mutex<()>,
}

impl A {
    pub fn new() -> A {
        A {
            map: BTreeMap::new(),
            mutex: Mutex::new(()),
        }
    }
}

impl A {
    fn synchronized_call(&mut self) {
        let mutex_guard_res = self.mutex.try_lock();
        if mutex_guard_res.is_err() {
            return
        }
        let mut _mutex_guard = mutex_guard_res.unwrap(); // safe because of check above
        let mut lambda = |text: String| {
            let _ = self.map.insert("hello".to_owned(),
                                    "d".to_owned());
        };
        lambda("dd".to_owned());
    }
}    

错误信息:

error[E0500]: closure requires unique access to `self` but `self.mutex` is already borrowed
  --> <anon>:23:26
   |
18 |         let mutex_guard_res = self.mutex.try_lock();
   |                               ---------- borrow occurs here
...
23 |         let mut lambda = |text: String| {
   |                          ^^^^^^^^^^^^^^ closure construction occurs here
24 |             if let Some(m) = self.map.get(&text) {
   |                              ---- borrow occurs due to use of `self` in closure
...
31 |     }
   |     - borrow ends here

据我所知,当我们从结构中借用任何东西时,在借用完成之前我们无法使用其他结构的字段。但是我该如何进行方法同步呢?

闭包需要对 self.map 的可变引用才能向其中插入内容。但是闭包捕获只适用于整个绑定。这意味着,如果你说 self.map,闭包会尝试捕获 self,而不是 self.map。而且 self 不能可变 borrowed/captured,因为 self 的一部分已经被不可变地借用了。

我们可以通过单独为地图引入新的绑定来解决这个闭包捕获问题,这样闭包就可以捕获它 (Playground):

let mm = &mut self.map;
let mut lambda = |text: String| {
    let _ = mm.insert("hello".to_owned(), text);
};
lambda("dd".to_owned());

但是,有一点你忽略了:因为synchronized_call()接受&mut self,你不需要互斥体!为什么? 可变引用也称为独占引用,因为编译器可以在编译时确保在任何给定时间只有一个这样的可变引用。

因此你静态地知道,在任何给定时间在一个特定对象上最多有一个synchronized_call() 运行实例,if 该函数不是递归的(调用自身)。

如果您对互斥锁具有可变访问权限,您就知道该互斥锁已解锁。参见 the Mutex::get_mut() method for more explanation。是不是很神奇?

Rust 互斥量与您尝试使用它们的方式不同。在 Rust 中,互斥量依赖于语言中其他地方使用的借用检查机制来保护特定数据。因此,声明一个字段 Mutex<()> 没有意义,因为它保护了对 () 单元对象的读写访问,该单元对象没有值可以改变。

正如 Lukas 所解释的,您声明的 call_synchronized 不需要进行同步,因为它的签名已经请求对 self 的独占(可变)引用,这可以防止它被多个调用同一对象上的线程。换句话说,您需要更改 call_synchronizedsignature,因为当前的签名与它打算提供的功能不匹配。

call_synchronized 需要接受对 self 的共享引用,这将首先向 Rust 发出可以从多个线程调用的信号。在 call_synchronized 内,对 Mutex::lock 的调用将同时锁定互斥锁并提供对底层数据的可变引用,仔细限定范围以便在引用期间保持锁定:

use std::sync::Mutex;
use std::collections::BTreeMap;

pub struct A {
    synced_map: Mutex<BTreeMap<String, String>>,
}

impl A {
    pub fn new() -> A {
        A {
            synced_map: Mutex::new(BTreeMap::new()),
        }
    }
}

impl A {
    fn synchronized_call(&self) {
        let mut map = self.synced_map.lock().unwrap();
        // omitting the lambda for brevity, but it would also work
        // (as long as it refers to map rather than self.map)
        map.insert("hello".to_owned(), "d".to_owned());
    }
}