面试题答案
一键面试优化方法
- 调整锁的粒度:
- 粗粒度锁:如果整个数据结构都由一个读写锁保护,当读操作很多时,读锁竞争会很激烈。可以将数据结构按逻辑或物理划分成多个部分,每个部分使用独立的读写锁。这样不同部分的读操作可以并行进行,减少竞争。
- 使用更细粒度的同步机制:
- 无锁数据结构:对于某些场景,可以使用无锁数据结构,如无锁队列、无锁哈希表等。这些数据结构通过原子操作和特殊的设计来避免锁的使用,从而提高并发性能。例如,
crossbeam - utils
库提供了一些无锁数据结构。 - 读写锁替代方案:对于读多写少的场景,
RwLock
并不是唯一选择。Arc<AtomicUsize>
结合Cell
或RefCell
在某些情况下可以实现类似的功能,并且由于原子操作的特性,可能在性能上更优。
- 无锁数据结构:对于某些场景,可以使用无锁数据结构,如无锁队列、无锁哈希表等。这些数据结构通过原子操作和特殊的设计来避免锁的使用,从而提高并发性能。例如,
代码示例
- 调整锁的粒度示例:
use std::sync::{Arc, RwLock};
// 假设这是我们的数据结构
struct BigData {
part1: Vec<i32>,
part2: Vec<i32>,
}
fn main() {
let big_data = Arc::new(BigData {
part1: vec![1, 2, 3],
part2: vec![4, 5, 6],
});
let lock1 = Arc::new(RwLock::new(()));
let lock2 = Arc::new(RwLock::new(()));
// 模拟并发读操作
let mut handles = vec![];
for _ in 0..10 {
let big_data_clone = big_data.clone();
let lock1_clone = lock1.clone();
let lock2_clone = lock2.clone();
let handle = std::thread::spawn(move || {
{
let _read_guard1 = lock1_clone.read().unwrap();
// 读取 part1
let part1 = &big_data_clone.part1;
println!("Read part1: {:?}", part1);
}
{
let _read_guard2 = lock2_clone.read().unwrap();
// 读取 part2
let part2 = &big_data_clone.part2;
println!("Read part2: {:?}", part2);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
在这个示例中,BigData
结构被分成两部分,分别由lock1
和lock2
保护,不同部分的读操作可以并行进行,减少了锁竞争。
- 使用无锁数据结构示例(以无锁队列为例):
use crossbeam_queue::ArrayQueue;
use std::thread;
fn main() {
let queue = Arc::new(ArrayQueue::new(10));
let mut handles = vec![];
for _ in 0..10 {
let queue_clone = queue.clone();
let handle = thread::spawn(move || {
queue_clone.push(1).unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let mut sum = 0;
while let Some(value) = queue.pop() {
sum += value;
}
println!("Sum: {}", sum);
}
这里使用crossbeam - queue
库中的ArrayQueue
无锁队列,在并发场景下,多个线程可以无锁地向队列中推送元素,避免了传统锁带来的竞争问题。