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)
我认为这是与寿命'a的MutexGuard中的返回值get_pool。坦率地说,我可能对这些生命周期参数的工作方式有些困惑。
问题出在这一行:
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)
编辑
根据要求,我将解释我是如何诊断此问题的;
println!("not here...");即可 100% 确定它挂在上面的语句处,而不是挂在块的末尾。Obj::new();必须调用两次才能发生问题。因此,下一个目标是找到两个调用之间的差异。(我对 Rust 的了解还不够好,还不足以通过阅读代码来发现这个错误)。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那里创建了一个额外的东西。请注意,rust 借用范围目前是词法的。get_pool()回想起来,如果我删除包含in的行并计算调用drop()次数,我会更早发现这一点。drop()我没有意识到被调用了三次而不是两次。一般而言,这个问题的标题是“为什么互斥锁不解锁”。这可以解释为编译器错误或标准库中的错误。大多数时候(>99%)并非如此。记住这一点很重要,不要关注错误的问题。
这个问题与全局共享状态有关。尽量避免这种情况。(是的,我知道这并不总是可能)。
| 归档时间: |
|
| 查看次数: |
1875 次 |
| 最近记录: |