我正在尝试为大型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。谢谢你的帮助和圣诞快乐。
答案 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.");
}
修改强>
根据要求,我会解释我是如何诊断的;
println!("not here...");
100%确定它挂在上面的语句中,而不是在块的末尾。Obj::new();
两次才能解决问题。所以下一个目标是找到两个调用之间的差异。 (我的生锈知识还不够好,只是通过阅读代码来发现这个错误。)POOL
未在第一次调用中初始化,我在main(unsafe{INIT.call_once(||{POOL=Some(Mutex::new(vec![]));});}
)的开头添加了初始化,但这并没有改变任何内容。Obj
时将对象添加到池中,所以我在main(get_pool().push(Obj {});
)的开头添加了一个对象。现在它挂在第一个Obj::new();
。get_pool().pop().unwrap_or(Obj {});
来进一步简化它。Obj
。请注意,防锈借用范围目前为lexical。get_pool()
中包含drop()
的行,并计算了drop()
被调用的次数,我会更早发现这一点。我没有意识到被叫三次而不是两次。总的来说,这个问题的标题是“为什么不是Mutex解锁”。这可能被解释为编译器错误或标准库中的错误。大部分时间(> 99%)不是。重要的是要牢记这一点,而不是关注错误的问题。
此问题与全局共享状态有关。尽量避免这种情况。 (是的,我知道并非总是可行)。