Вторая блокировка одной и той же переменной в одном выражении блокирует на неопределенный срок - PullRequest
0 голосов
/ 06 октября 2018

У меня есть 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 -статменте, код в этом операторе никогда не выполняется.

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 ...
        })
    }
}

Существует ли какая-либо конкретная причина, по которой поведение Руста может ждать бесконечно в первом случае?

1 Ответ

0 голосов
/ 08 октября 2018

Вот 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);
}

детская площадка

При запуске этого кода он напечатает f1 и f2, затем зависнетпри попытке вычислить 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 ...
        })
    }
}
...