在单个表达式中对同一变量的第二次锁定无限期地阻塞
Second lock on same variable in a single expression blocks indefinitely
我有一个 Node
包含共享 Protocol
上的 Mutex
,它又在线程池中的不同线程之间使用:
use std::sync::{Arc, Mutex};
pub struct Node {
thread_pool: ThreadPool,
protocol: Arc<Mutex<Protocol>>,
}
pub struct Protocol {}
impl Protocol {
pub fn is_leader(&self) -> bool {
// Do stuff...
}
pub fn is_co_leader(&self) -> bool {
// Do stuff...
}
}
当我尝试在同一 if
语句中获取 Node
的 protocol
上的锁时,该语句中的代码永远不会执行。
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
if !protocol_handler.lock().unwrap().is_leader()
&& !protocol_handler.lock().unwrap().is_co_leader()
{
// This is never executed
}
// And this neither...
})
}
}
但是,如果将方法调用的值分配给两个变量,一切都会按预期进行:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let is_leader = protocol_handler.lock().unwrap().is_leader();
let is_co_leader = protocol_handler.lock().unwrap().is_co_leader();
if !is_leader && !is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}
在第一种情况下,Rust 的行为无限期等待是否有任何具体原因?
这是针对您的问题的 MCVE:
use std::sync::Mutex;
fn main() {
let foo = Mutex::new(42i32);
let f1 = (*foo.lock().unwrap()).count_ones();
println!("f1: {}", f1);
let f2 = (*foo.lock().unwrap()).count_zeros();
println!("f2: {}", f2);
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
println!("tot: {}", tot);
}
当 运行 此代码将打印 f1
和 f2
,然后在尝试计算 tot
.
时挂起
问题是 Mutex::lock
returns 一个 MutexGuard
会在超出范围时自动释放锁。在上面的示例中,守卫在使用它们的表达式末尾超出范围。所以当我写:
let f1 = (*foo.lock().unwrap()).count_ones();
我获取了锁,读取了值,然后释放了锁。因此在计算 f2
.
时锁是空闲的
然而,当我写:
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
我获取了锁,读取了值,尝试再次获取锁并且只在行尾释放了两个守卫。当我在没有先释放锁的情况下第二次尝试获取锁时,这会导致代码死锁。
请注意,正如 trentcl 所评论的那样,如果互斥体被锁定的两次之间发生了变化,则您的两步示例会受到竞争条件的影响。你应该使用这样的东西:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let handler = protocol_handler.lock().unwrap();
if !handler.is_leader && !handler.is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}
我有一个 Node
包含共享 Protocol
上的 Mutex
,它又在线程池中的不同线程之间使用:
use std::sync::{Arc, Mutex};
pub struct Node {
thread_pool: ThreadPool,
protocol: Arc<Mutex<Protocol>>,
}
pub struct Protocol {}
impl Protocol {
pub fn is_leader(&self) -> bool {
// Do stuff...
}
pub fn is_co_leader(&self) -> bool {
// Do stuff...
}
}
当我尝试在同一 if
语句中获取 Node
的 protocol
上的锁时,该语句中的代码永远不会执行。
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
if !protocol_handler.lock().unwrap().is_leader()
&& !protocol_handler.lock().unwrap().is_co_leader()
{
// This is never executed
}
// And this neither...
})
}
}
但是,如果将方法调用的值分配给两个变量,一切都会按预期进行:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let is_leader = protocol_handler.lock().unwrap().is_leader();
let is_co_leader = protocol_handler.lock().unwrap().is_co_leader();
if !is_leader && !is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}
在第一种情况下,Rust 的行为无限期等待是否有任何具体原因?
这是针对您的问题的 MCVE:
use std::sync::Mutex;
fn main() {
let foo = Mutex::new(42i32);
let f1 = (*foo.lock().unwrap()).count_ones();
println!("f1: {}", f1);
let f2 = (*foo.lock().unwrap()).count_zeros();
println!("f2: {}", f2);
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
println!("tot: {}", tot);
}
当 运行 此代码将打印 f1
和 f2
,然后在尝试计算 tot
.
问题是 Mutex::lock
returns 一个 MutexGuard
会在超出范围时自动释放锁。在上面的示例中,守卫在使用它们的表达式末尾超出范围。所以当我写:
let f1 = (*foo.lock().unwrap()).count_ones();
我获取了锁,读取了值,然后释放了锁。因此在计算 f2
.
然而,当我写:
let tot = (*foo.lock().unwrap()).count_ones() + (*foo.lock().unwrap()).count_zeros();
我获取了锁,读取了值,尝试再次获取锁并且只在行尾释放了两个守卫。当我在没有先释放锁的情况下第二次尝试获取锁时,这会导致代码死锁。
请注意,正如 trentcl 所评论的那样,如果互斥体被锁定的两次之间发生了变化,则您的两步示例会受到竞争条件的影响。你应该使用这样的东西:
impl Node {
pub fn sign(&mut self) {
let protocol_handler = Arc::clone(&self.protocol);
self.thread_pool.execute(move || {
let handler = protocol_handler.lock().unwrap();
if !handler.is_leader && !handler.is_co_leader {
// Either this will be executed
}
// or this ...
})
}
}