为什么Mutex没有解锁?

时间:2016-12-25 18:21:04

标签: concurrency rust mutex pool

我正在尝试为大型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...
    }
}

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

这是一个带有工作示例的link to a playground。谢谢你的帮助和圣诞快乐。

1 个答案:

答案 0 :(得分:4)

问题出在以下一行:

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

因为您拨打get_pool(),所以您锁定互斥锁,直到该行结束才会解锁。但是,在致电unwrap_or()时,您会创建一个新的Obj。如果vec中有对象,则不会使用此方法。因为它是在以后创建的,所以在释放互斥锁之前它将被删除。当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();两次才能解决问题。所以下一个目标是找到两个调用之间的差异。 (我的生锈知识还不够好,只是通过阅读代码来发现这个错误。)
  3. 由于POOL未在第一次调用中初始化,我在main(unsafe{INIT.call_once(||{POOL=Some(Mutex::new(vec![]));});})的开头添加了初始化,但这并没有改变任何内容。
  4. 因为在删除Obj时将对象添加到池中,所以我在main(get_pool().push(Obj {});)的开头添加了一个对象。现在它挂在第一个Obj::new();
  5. 我可以通过调用main中的get_pool().pop().unwrap_or(Obj {});来进一步简化它。
  6. 现在,我可以部分删除或拆分该行以确定其挂起的确切位置。通过这样做,我看到我能够解决它。然后我意识到在那里创建了一个额外的Obj。请注意,防锈借用范围目前为lexical
  7. 回想起来,如果我删除了get_pool()中包含drop()的行,并计算了drop()被调用的次数,我会更早发现这一点。我没有意识到被叫三次而不是两次。
  8. 总的来说,这个问题的标题是“为什么不是Mutex解锁”。这可能被解释为编译器错误或标准库中的错误。大部分时间(> 99%)不是。重要的是要牢记这一点,而不是关注错误的问题。

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