面试题答案
一键面试可能导致死锁的场景
假设有两个线程 thread1
和 thread2
,每个线程都尝试获取两个不同的资源 resource1
和 resource2
,并且获取资源的操作封装在闭包中。如果 thread1
先获取 resource1
,然后尝试获取 resource2
,而 thread2
先获取 resource2
,然后尝试获取 resource1
,就可能会发生死锁。
以下是示例代码:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let resource1 = Arc::new(Mutex::new(0));
let resource2 = Arc::new(Mutex::new(0));
let resource1_clone = Arc::clone(&resource1);
let resource2_clone = Arc::clone(&resource2);
let handle1 = thread::spawn(move || {
let _lock1 = resource1_clone.lock().unwrap();
// 模拟一些工作
thread::sleep(std::time::Duration::from_millis(100));
let _lock2 = resource2_clone.lock().unwrap();
});
let handle2 = thread::spawn(move || {
let _lock2 = resource2.lock().unwrap();
// 模拟一些工作
thread::sleep(std::time::Duration::from_millis(100));
let _lock1 = resource1.lock().unwrap();
});
handle1.join().unwrap();
handle2.join().unwrap();
}
在这个例子中,thread1
先锁定 resource1
,再锁定 resource2
,而 thread2
先锁定 resource2
,再锁定 resource1
。如果 thread1
在锁定 resource1
后,thread2
在锁定 resource2
后,双方都尝试获取对方已锁定的资源,就会导致死锁。
避免死锁的设计及理论分析
为了避免死锁,可以采用资源获取顺序一致的原则。即所有线程都按照相同的顺序获取资源。
以下是修改后的代码,使用 Mutex
来保证资源安全,并遵循一致的资源获取顺序:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let resource1 = Arc::new(Mutex::new(0));
let resource2 = Arc::new(Mutex::new(0));
let resource1_clone = Arc::clone(&resource1);
let resource2_clone = Arc::clone(&resource2);
let handle1 = thread::spawn(move || {
let _lock1 = resource1_clone.lock().unwrap();
// 模拟一些工作
thread::sleep(std::time::Duration::from_millis(100));
let _lock2 = resource2_clone.lock().unwrap();
});
let handle2 = thread::spawn(move || {
let _lock1 = resource1.lock().unwrap();
// 模拟一些工作
thread::sleep(std::time::Duration::from_millis(100));
let _lock2 = resource2.lock().unwrap();
});
handle1.join().unwrap();
handle2.join().unwrap();
}
理论分析:通过让所有线程都先获取 resource1
,再获取 resource2
,就不会出现相互等待对方释放资源的情况,从而避免了死锁。
如果资源读写操作较多,可以考虑使用 RwLock
。RwLock
允许多个线程同时读,但只允许一个线程写。在使用 RwLock
时,同样要遵循一致的资源获取顺序。
示例代码如下:
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let resource1 = Arc::new(RwLock::new(0));
let resource2 = Arc::new(RwLock::new(0));
let resource1_clone = Arc::clone(&resource1);
let resource2_clone = Arc::clone(&resource2);
let handle1 = thread::spawn(move || {
let _read_lock1 = resource1_clone.read().unwrap();
// 模拟读操作
thread::sleep(std::time::Duration::from_millis(100));
let _read_lock2 = resource2_clone.read().unwrap();
});
let handle2 = thread::spawn(move || {
let _read_lock1 = resource1.lock().unwrap();
// 模拟写操作
thread::sleep(std::time::Duration::from_millis(100));
let _read_lock2 = resource2.lock().unwrap();
});
handle1.join().unwrap();
handle2.join().unwrap();
}
在这个示例中,无论是读操作还是写操作,线程都按照相同的顺序获取资源,避免了死锁,同时 RwLock
也保证了并发读写的安全。