面试题答案
一键面试锁机制(如Mutex
)
- 优点:
- 通用性强:能保护任何类型的数据结构,无论其复杂程度如何,只要将数据包装在
Mutex
中,就能确保同一时间只有一个线程可以访问该数据,有效避免数据竞争。 - 细粒度控制:可根据需求在代码块级别进行加锁解锁操作,灵活控制线程对共享资源的访问时机。
- 通用性强:能保护任何类型的数据结构,无论其复杂程度如何,只要将数据包装在
- 缺点:
- 性能开销:加锁解锁操作相对较重,涉及操作系统内核态与用户态的切换(在某些实现中),如果频繁加锁解锁,会带来较大的性能损耗。
- 死锁风险:如果多个线程以不同顺序获取多个锁,可能会导致死锁,即所有线程都在等待对方释放锁,从而造成程序挂起。
原子操作
- 优点:
- 高性能:原子操作由硬件直接支持,通常在用户态即可完成,无需像锁那样进行内核态与用户态的切换,对于简单数据类型的操作性能很高。
- 无死锁风险:因为原子操作是不可分割的,不会出现多个线程互相等待锁的情况,所以不存在死锁问题。
- 缺点:
- 数据类型受限:只能对特定的简单数据类型(如
i32
、u64
等整数类型)进行原子操作,无法直接用于复杂的数据结构。 - 功能局限性:原子操作只能完成一些简单的算术运算或位运算,对于复杂的逻辑操作,原子操作无法直接满足需求。
- 数据类型受限:只能对特定的简单数据类型(如
优先选择原子操作的场景
- 计数器场景:例如在多线程环境下统计某个事件的发生次数。
use std::sync::atomic::{AtomicU32, Ordering};
use std::thread;
fn main() {
let counter = AtomicU32::new(0);
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
for _ in 0..100 {
counter_clone.fetch_add(1, Ordering::Relaxed);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final counter value: {}", counter.load(Ordering::Relaxed));
}
在此场景中,使用原子操作AtomicU32
进行计数器的增加操作,简单高效,无需使用锁来保护共享的计数器。
优先选择锁的场景
- 复杂数据结构共享场景:比如多个线程需要访问和修改一个复杂的自定义结构体,结构体中包含多种不同类型的成员变量和复杂的逻辑操作。
use std::sync::{Arc, Mutex};
use std::thread;
struct ComplexData {
data1: i32,
data2: String,
// 更多复杂数据成员
}
fn main() {
let complex_data = Arc::new(Mutex::new(ComplexData {
data1: 0,
data2: String::from("initial"),
}));
let mut handles = vec![];
for _ in 0..10 {
let complex_data_clone = complex_data.clone();
let handle = thread::spawn(move || {
let mut data = complex_data_clone.lock().unwrap();
data.data1 += 1;
data.data2.push_str(" modified");
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let final_data = complex_data.lock().unwrap();
println!("Final data1: {}, Final data2: {}", final_data.data1, final_data.data2);
}
在这种情况下,由于数据结构复杂,原子操作无法满足需求,使用Mutex
可以确保同一时间只有一个线程能访问和修改这个复杂数据结构,保证数据的一致性和完整性。