为什么互斥锁不解锁?

eth*_*oks 5 concurrency mutex pool rust

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

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

pub struct Obj;
Run Code Online (Sandbox Code Playgroud)

这是我访问和锁定的方式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()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是导致问题的代码:

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...
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这是与寿命'aMutexGuard中的返回值get_pool。坦率地说,我可能对这些生命周期参数的工作方式有些困惑。

这是带有工作示例的操场链接。感谢您的帮助和圣诞快乐。

wim*_*ica 4

问题出在这一行:

get_pool().pop().unwrap_or(Obj {})
Run Code Online (Sandbox Code Playgroud)

因为您调用get_pool(),所以您锁定了互斥体,并且直到行尾它才会被解锁。然而,在调用 to 时unwrap_or(),您创建了一个新的Obj. 如果 vec 中有对象,则不会使用此方法。因为它是稍后创建的,所以在释放互斥锁之前它将被删除。当 drop 尝试锁定互斥体时,就会陷入死锁。

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

let o = get_pool().pop();
o.unwrap_or(Obj {})
Run Code Online (Sandbox Code Playgroud)

作为相关说明,您可以使用惰性静态来避免不安全的代码:

#![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.");
}
Run Code Online (Sandbox Code Playgroud)

编辑

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

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

一般而言,这个问题的标题是“为什么互斥锁不解锁”。这可以解释为编译器错误或标准库中的错误。大多数时候(>99%)并非如此。记住这一点很重要,不要关注错误的问题。

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