为什么 Mutex 没有解锁?

Why isn't Mutex unlocking?

我正在尝试为大型 Obj 类型实现一个全局对象池。这是 POOL:

的代码
static mut POOL: Option<Mutex<Vec<Obj>>> = None;
static INIT: Once = ONCE_INIT;

pub struct Obj;

这是我访问和锁定的方式 POOL:

fn get_pool<'a>() -> MutexGuard<'a, Vec<Obj>> {
    unsafe {
        match POOL {
            Some(ref mutex) => mutex.lock().unwrap(),
            None            => {
                INIT.call_once(|| {
                    POOL = Some(Mutex::new(vec![]));
                });
                get_pool()
            }
        }
    }
}

这是导致问题的代码:

impl Drop for Obj {
    fn drop(&mut self) {
        println!("dropping.");
        println!("hangs here...");
        get_pool().push(Obj {});
    }
}

impl Obj {
    pub fn new() -> Obj {
        println!("initializing");
        get_pool().pop().unwrap_or(Obj {})
        // for some reason, the mutex does not get unlocked at this point...
    }
}

我认为这与get_pool的return值中MutexGuard的生命周期'a有关。坦率地说,我可能对这些生命周期参数的工作方式有点困惑。

这是一个 link to a playground 的工作示例。感谢您的帮助,祝您圣诞快乐。

问题出在这一行:

get_pool().pop().unwrap_or(Obj {})

因为你调用了get_pool(),你锁定了互斥锁,直到行尾才会解锁。但是在调用 unwrap_or() 时,您创建了一个新的 Obj。如果 vec 中有 object,则不会使用它。因为它是稍后创建的,所以在释放互斥量之前它会被丢弃。当 drop 尝试锁定互斥锁时,您会遇到死锁。

要解决此问题,请将该语句分成两行:

let o = get_pool().pop();
o.unwrap_or(Obj {})

作为相关说明,您可以使用 lazy-static 来避免不安全的代码:

#![feature(drop_types_in_const)]
use std::sync::{Mutex, MutexGuard};

#[macro_use]
extern crate lazy_static;

lazy_static! {
  static ref POOL: Mutex<Vec<Obj>> = Mutex::new(vec![]);
}

pub struct Obj;

fn get_pool<'a>() -> MutexGuard<'a, Vec<Obj>> {
        POOL.lock().unwrap()
}

impl Drop for Obj {
    fn drop(&mut self) {
        println!("dropping.");
        println!("hangs here...");
        get_pool().push(Obj {});
        println!("not here...");
    }
}

impl Obj {
    pub fn new() -> Obj {
        println!("initializing");
        let o = get_pool().pop();
        o.unwrap_or(Obj {})
    }
}

fn main() {
    Obj::new();
    Obj::new();
    println!("Now reaches this point.");
}

编辑

根据要求,我将解释我是如何诊断出这个问题的;

  1. 首先我验证了我是否可以使用您提供的示例重现该问题。我可以,而且代码非常简单明了。这一切都很好,我只需要添加行 println!("not here..."); 就可以 100% 确定它挂在上面的语句中,而不是在块的末尾。
  2. 在第一次扫描中,我注意到必须调用 Obj::new(); 两次才能出现问题。所以下一个目标是找到这两个调用之间的差异。 (我对 Rust 的了解还不足以通过阅读代码来发现这个错误)。
  3. 因为 POOL 没有在第一次调用时初始化,所以我在 main (unsafe{INIT.call_once(||{POOL=Some(Mutex::new(vec![]));});}) 的开头添加了初始化,但这并没有改变任何东西。
  4. 因为在删除 Obj 时会向池中添加一个 object,所以我在 main (get_pool().push(Obj {});) 的开头添加了一个 object。现在它挂在第一个 Obj::new();.
  5. 我可以通过在 main 中调用 get_pool().pop().unwrap_or(Obj {}); 来进一步简化它。
  6. 现在我可以部分删除或拆分该行以确定它挂起的确切位置。通过这样做,我看到我能够修复它。然后我意识到那里创建了一个额外的 Obj 。请注意,rust 借用范围目前是 lexical.
  7. 回想起来,如果我删除 drop() 中包含 get_pool() 的行并计算 drop() 被调用了多少次,我会更早地发现这一点。我没有意识到它被调用了三次而不是两次。

一般来说,这个问题的标题是"Why isn't Mutex unlocking"。这可能被解释为编译器错误或标准库中的错误。大多数时候 (>99%) 不是。记住这一点很重要,不要关注错误的问题。

此问题与全局共享状态有关。尽量避免这种情况。 (是的,我知道这并不总是可能的)。