如何在使用防护时避免互斥借用问题

时间:2016-12-17 09:15:41

标签: rust mutex

我希望我的struct方法以同步方式执行。我希望使用MutexPlayground):

来完成此操作
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

据我所知,当我们从结构中借用任何东西时,我们无法使用其他结构的字段,直到我们的借用完成。但是我怎样才能进行方法同步?

2 个答案:

答案 0 :(得分:6)

闭包需要对self.map的可变引用才能在其中插入内容。但是闭包捕获仅适用于整个绑定。这意味着,如果您说self.map,则关闭会尝试捕获self,而不是self.map。并且self不能被可变地借用/捕获,因为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()实例运行,如果该函数不是递归(调用自身)。

如果您具有对互斥锁的可变访问权限,则表示该互斥锁已解锁。见the Mutex::get_mut() method for more explanation。难道不是很棒吗?

答案 1 :(得分:4)

Rust互斥量不会像您尝试使用它们那样工作。在Rust中,互斥锁保护依赖于语言中其他地方使用的借用检查机制的特定数据。因此,声明字段Mutex<()>没有意义,因为它保护了对没有值变异的()单元对象的读写访问权。

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

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());
    }
}