< 返回版块

viruscamp 发表于 2021-03-20 15:35

代码如下。
意图是实现 no_lock 函数的带锁的版本。
dead_lock_1 函数 死锁了,我能想明白其中的临时 MutexGuard 有效期包括整个 while,导致内部拿不到锁。
dead_lock_2 函数 加个作用域,但还是死锁了。想不明白,临时变量能活过我新加的作用域?
no_dead_lock 函数 加个作用域,再写清楚变量。终于好了,这个是没有问题的。


结论就是:
{ vec_mutex.lock().unwrap().pop() } 是个块表达式, block expression, 虽然有个大括号,但不负责 drop 临时变量。
{ let mut vec = vec_mutex.lock().unwrap(); vec.pop() } 这里的临时变量是里面的语句, statement, 有分号的那个负责 drop 的。

use std::sync::Mutex;

fn main() {
    dead_lock_2();
}

fn no_lock() {
    let mut vec = vec![1,2,3];
    while let Some(num) = vec.pop() {
        if num == 2 {
            vec.push(4);
        }
        println!("got {}", num);
    }
}

fn dead_lock_1() {
    let vec_mutex = Mutex::new(vec![1,2,3]);
    // 会导致 MutexGuard 锁的有效期包括整个 while 导致死锁
    while let Some(num) = vec_mutex.lock().unwrap().pop() {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

fn dead_lock_2() {
    let vec_mutex = Mutex::new(vec![1,2,3]);
    // 这也会 为什么?
    while let Some(num) = { vec_mutex.lock().unwrap().pop() } {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

fn no_dead_lock() {
    let vec_mutex = Mutex::new(vec![1,2,3]);
    // 终于不会死锁了,但跟 dead_lock_2 有什么区别
    while let Some(num) = {
        let mut vec = vec_mutex.lock().unwrap();
        vec.pop()
    } {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

评论区

写评论
作者 viruscamp 2021-03-20 20:44

我觉得这个能说通。
然后根据这个理论,构造出下面这种死锁。

fn dead_lock_3() {
    let vec = Mutex::new(vec![1,2,3]);
    while let Some(num) = {
        let x = vec.lock(); // statement 没有临时变量
        x.unwrap().pop() // 有临时变量 但没有 statement, drop 延迟
    } {
        if num == 2 {
            vec.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

--
👇
7sDream: 我可能懂了。

https://doc.rust-lang.org/stable/reference/expressions.html#temporaries

这里介绍了临时变量的 drop 时机:

When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead, except if promoted to a static. The drop scope of the temporary is usually the end of the enclosing statement.

这里的关键词是 statement

在 https://doc.rust-lang.org/stable/reference/statements.html 这里可以看到,statement 只有五种

; | Item | LetStatement | ExpressionStatement | MacroInvocationSemi

{ expr } 这种形式并不是一个 statement。

根据 https://doc.rust-lang.org/stable/reference/expressions/block-expr.html,这其实是一个 Block expressions。

所以在第二种写法下,由于一直没有一个 statement 出现,那个 MutexGuard 的 drop 时机被放到了 while 语句结束时。

而在后面那种作用于中有语句的情况下,就在当前那条语句的末尾了。

7sDream 2021-03-20 19:58

我可能懂了。

https://doc.rust-lang.org/stable/reference/expressions.html#temporaries

这里介绍了临时变量的 drop 时机:

When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead, except if promoted to a static. The drop scope of the temporary is usually the end of the enclosing statement.

这里的关键词是 statement

在 https://doc.rust-lang.org/stable/reference/statements.html 这里可以看到,statement 只有五种

; | Item | LetStatement | ExpressionStatement | MacroInvocationSemi

{ expr } 这种形式并不是一个 statement。

根据 https://doc.rust-lang.org/stable/reference/expressions/block-expr.html,这其实是一个 Block expressions。

所以在第二种写法下,由于一直没有一个 statement 出现,那个 MutexGuard 的 drop 时机被放到了 while 语句结束时。

而在后面那种作用于中有语句的情况下,就在当前那条语句的末尾了。

7sDream 2021-03-20 19:49
fn dead_lock_1() {
    let vec_mutex = MockMutex::new(vec![1, 2, 3]);
    // 会导致 MutexGuard 锁的有效期包括整个 while 导致死锁
    while let Some(num) = {
        let n = vec_mutex.lock().unwrap().pop();
        println!("After pop");
        n
    } {
        println!("Enter loop body");
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

还是用之前那个 mock 代码,这样的话输出是:

MockGuard dropped
After pop
Enter loop body
got 3
……

所以这样的话是在用完之后立刻就 drop 了,甚至没有等到作用域结束。

更奇妙了感觉。

7sDream 2021-03-20 19:37

挺有趣的一个问题,确实感觉 dead_lock_2 这种在 while 条件里加了作用域的情况下不应该死锁的。

只可能是 MutexGuard 并没有在那个作用域结束时立即被 drop 了。

写了个代码验证下:

use std::ops::{Deref, DerefMut};

struct MockMutex<T>(T);

struct MockGuard<'a, T>(&'a mut T);

impl<T> MockMutex<T> {
    fn new(value: T) -> Self {
        Self(value)
    }

    fn lock(&self) -> Result<MockGuard<'_, T>, ()> {
        Ok(MockGuard(unsafe {
            (&self.0 as *const T as *mut T).as_mut().unwrap()
        }))
    }
}

impl<T> Deref for MockGuard<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<T> DerefMut for MockGuard<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.0
    }
}

impl<T> Drop for MockGuard<'_, T> {
    fn drop(&mut self) {
        println!("MockGuard dropped");
    }
}

fn dead_lock_1() {
    let vec_mutex = MockMutex::new(vec![1, 2, 3]);
    // 这也会 为什么?
    while let Some(num) = { vec_mutex.lock().unwrap().pop() } {
        println!("Enter loop body");
        if num == 2 {
            // vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

fn main() {
    dead_lock_1()
}

输出结果:

Enter loop body
got 3
MockGuard dropped
Enter loop body
got 2
MockGuard dropped
Enter loop body
got 1
MockGuard dropped
MockGuard dropped

结果是 MockGuard droppedgot n 之后才会出现,也就是一轮 loop 结束后才会被 drop

原因是知道了,至于为啥是这样,确实比较迷惑。

Blues-star 2021-03-20 17:05

个人见解,no_dead_lock()函数中大括号里的let表达式会引入一个新的作用域, vec获得锁后在第一层大括号的作用域里,但是经过let表达式后,返回给num的值是vec转移到let表达式产生的隐式的作用域中pop出来的值,返回后vec这个变量实际上是被drop掉的。

Neutron3529 2021-03-20 16:12

或许可以这样:

while let Some(num) = ||(vec_mutex.lock().unwrap().pop())(){...}

原理仍然是释放临时变量

Neutron3529 2021-03-20 16:03
fn no_dead_lock() {
    let vec_mutex = Mutex::new(vec![1,2,3]);
    // 终于不会死锁了,但跟 dead_lock_2 有什么区别
    while let Some(num) = {
        let mut vecpop = vec_mutex.lock().unwrap().pop();
        vecpop
    } {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}

我只知道这样也不会死锁

大概是临时变量在语句结束之后才drop的缘故

.lock生成的临时变量在语句(一次while循环)结束之后才drop,这导致了死锁。

加分号的意思大概是让临时变量在分号之后直接drop,这样可以将解决死锁问题。

作者 viruscamp 2021-03-20 15:54

一个能解决问题的新写法,但更难解释。

fn no_dead_lock_2() {
    let vec_mutex = Mutex::new(vec![1,2,3]);
    // 这个跟 dead_lock_2 有什么区别
    // while let Some(num) = { vec_mutex.lock().unwrap().pop() }
    while let Some(num) = {
        let n = vec_mutex.lock().unwrap().pop();
        n
    } {
        if num == 2 {
            vec_mutex.lock().unwrap().push(4);
        }
        println!("got {}", num);
    }
}
1 共 8 条评论, 1 页