单个表达式中对同一变量的第二次锁定无限期地

时间:2018-10-06 10:11:38

标签: rust deadlock

我有一个Node,它在共享的Mutex上包含Protocol,而该use std::sync::{Arc, Mutex}; pub struct Node { thread_pool: ThreadPool, protocol: Arc<Mutex<Protocol>>, } pub struct Protocol {} impl Protocol { pub fn is_leader(&self) -> bool { // Do stuff... } pub fn is_co_leader(&self) -> bool { // Do stuff... } } 又在线程池中的不同线程之间使用:

protocol

当我尝试在相同的Node语句中获取if的{​​{1}}的锁时,该语句中的代码将永远不会执行。

impl Node {
    pub fn sign(&mut self) {
        let protocol_handler = Arc::clone(&self.protocol);

        self.thread_pool.execute(move || {
            if !protocol_handler.lock().unwrap().is_leader()
                && !protocol_handler.lock().unwrap().is_co_leader()
            {
                // This is never executed
            }

            // And this neither...
        })
    }
}

但是,如果将方法调用的值分配给两个变量,则一切都会按预期进行:

impl Node {
    pub fn sign(&mut self) {
        let protocol_handler = Arc::clone(&self.protocol);

        self.thread_pool.execute(move || {
            let is_leader = protocol_handler.lock().unwrap().is_leader();
            let is_co_leader = protocol_handler.lock().unwrap().is_co_leader();

            if !is_leader && !is_co_leader {
                // Either this will be executed
            }

            // or this ...
        })
    }
}

在第一种情况下,是否有任何特定原因导致Rust的行为无限期等待?

1 个答案:

答案 0 :(得分:2)

以下是您遇到的问题的MCVE:

use std::sync::Mutex;

fn main() {
    let foo = Mutex::new(42i32);

    let f1 = (*foo.lock().unwrap()).count_ones();
    println!("f1: {}", f1);
    let f2 = (*foo.lock().unwrap()).count_zeros();
    println!("f2: {}", f2);

    let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
    println!("tot: {}", tot);
}

playground

运行此代码时,它将打印f1f2,然后在尝试计算tot时挂起。

问题是Mutex::lock返回一个MutexGuard,当它超出范围时会自动释放锁。在上面的示例中,防护在使用它们的表达式的末尾超出了范围。所以当我写:

let f1 = (*foo.lock().unwrap()).count_ones();

我获取了锁,读取了值,然后释放了锁。因此,在计算f2时,锁是免费的。

但是,当我写的时候:

let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();

我获取了锁,读取了值,尝试再次获取了锁,只释放了行尾的两个防护。当我第二次尝试获取锁而没有先释放锁时,这会导致代码死锁。

请注意,如trentcl所评论,如果在两次锁定互斥锁之间都进行了更改,则您的两步示例将受竞争条件的影响。您应该使用这样的东西:

impl Node {
    pub fn sign(&mut self) {
        let protocol_handler = Arc::clone(&self.protocol);

        self.thread_pool.execute(move || {
            let handler = protocol_handler.lock().unwrap();

            if !handler.is_leader && !handler.is_co_leader {
                // Either this will be executed
            }

            // or this ...
        })
    }
}