面试题答案
一键面试1. 使用 std::sync
模块工具避免数据竞争
在Rust中,std::sync
模块提供了多种工具来确保多线程环境下的数据安全,避免数据竞争。
Mutex
(互斥锁):Mutex
用于保护共享数据,确保同一时间只有一个线程可以访问数据。线程需要先获取锁才能访问数据,访问结束后释放锁。例如:
use std::sync::Mutex;
fn main() {
let data = Mutex::new(0);
let handle = std::thread::spawn(move || {
let mut num = data.lock().unwrap();
*num += 1;
});
handle.join().unwrap();
let result = data.lock().unwrap();
println!("Result: {}", *result);
}
在上述代码中,Mutex::new(0)
创建了一个包裹着 0
的 Mutex
。data.lock().unwrap()
获取锁并返回一个智能指针,通过这个指针可以安全地访问和修改数据。访问完成后,智能指针离开作用域时自动释放锁。
Arc
(原子引用计数):Arc
用于在多线程环境下共享数据。它通过引用计数来管理数据的生命周期。结合Mutex
,可以在多个线程间安全地共享可变数据。例如:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared_data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
let mut num = data.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let result = shared_data.lock().unwrap();
println!("Final result: {}", *result);
}
这里,Arc::new(Mutex::new(0))
创建了一个被 Arc
包裹的 Mutex
,Arc
允许数据在多个线程间共享,Mutex
确保数据访问的安全性。Arc::clone
用于复制引用,使得每个线程都能持有对共享数据的引用。
2. 高并发场景下的死锁问题及解决方案
- 死锁问题:死锁发生在多个线程相互等待对方释放资源的情况下。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1,此时就会发生死锁。在多线程程序中,如果锁的获取顺序不一致,或者嵌套锁的使用不当,就容易导致死锁。
- 解决方案:
- 固定锁获取顺序:确保所有线程按照相同的顺序获取锁。例如,如果有锁A和锁B,所有线程都先获取锁A,再获取锁B,这样可以避免死锁。
- 使用
try_lock
:Mutex
提供了try_lock
方法,它尝试获取锁,如果锁不可用则立即返回Err
。通过使用try_lock
,线程可以在无法获取锁时选择放弃或采取其他操作,避免无限等待。例如:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let lock_a = Arc::new(Mutex::new(0));
let lock_b = Arc::new(Mutex::new(0));
let handle1 = thread::spawn(move || {
let a_lock = lock_a.try_lock();
if let Ok(mut num_a) = a_lock {
*num_a += 1;
let b_lock = lock_b.try_lock();
if let Ok(mut num_b) = b_lock {
*num_b += 1;
}
}
});
let handle2 = thread::spawn(move || {
let b_lock = lock_b.try_lock();
if let Ok(mut num_b) = b_lock {
*num_b += 1;
let a_lock = lock_a.try_lock();
if let Ok(mut num_a) = a_lock {
*num_a += 1;
}
}
});
handle1.join().unwrap();
handle2.join().unwrap();
}
在上述代码中,try_lock
方法使得线程在无法获取锁时不会无限等待,从而避免死锁。
3. 高并发操作的代码示例及说明
use std::sync::{Arc, Mutex};
use std::thread;
// 定义一个共享数据结构
struct SharedData {
value: i32,
counter: i32,
}
fn main() {
let shared = Arc::new(Mutex::new(SharedData { value: 0, counter: 0 }));
let mut handles = vec![];
for _ in 0..100 {
let shared_clone = Arc::clone(&shared);
let handle = thread::spawn(move || {
let mut data = shared_clone.lock().unwrap();
data.value += 1;
data.counter += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let result = shared.lock().unwrap();
println!("Final value: {}, Counter: {}", result.value, result.counter);
}
代码说明:
- 共享数据结构:定义了
SharedData
结构体,包含value
和counter
两个字段,用于演示多线程对多个数据字段的并发操作。 - 创建共享数据:
Arc::new(Mutex::new(SharedData { value: 0, counter: 0 }))
创建了一个被Arc
包裹的Mutex
,其中Mutex
保护着SharedData
实例,Arc
使得数据可以在多个线程间共享。 - 线程操作:通过循环创建100个线程,每个线程获取共享数据的锁,对
value
和counter
进行递增操作。 - 等待线程完成:
for handle in handles { handle.join().unwrap(); }
等待所有线程执行完毕,确保数据操作完成。 - 输出结果:最后获取锁并输出
value
和counter
的最终值,验证多线程操作的正确性。通过Mutex
和Arc
的配合使用,有效避免了数据竞争,确保高并发场景下数据的一致性和安全性。