面试题答案
一键面试设计思路
- 共享数据管理:使用
Arc
(原子引用计数)来共享数据,使得多个任务可以引用相同的数据。为了保证数据的线程安全读写,结合Mutex
或RwLock
。Mutex
用于独占访问(读写),RwLock
适用于读多写少的场景,允许多个读操作并发进行。 - 任务并发执行:利用
thread::spawn
创建多个线程来并行处理任务。为了收集每个线程的处理结果,可以使用JoinHandle
来等待线程完成并获取返回值。 - 减少锁竞争:尽量减少锁的持有时间,只在实际需要读写共享数据时才获取锁。可以通过将数据进行适当的分区,每个线程处理自己分区的数据,从而减少锁竞争。同时,避免不必要的锁嵌套。
- 缓存一致性:现代 CPU 缓存架构下,通过使用原子操作和合适的内存屏障(在 Rust 中由
Arc
和相关同步原语隐式处理)来保证缓存一致性。避免频繁地读写共享数据,尽量在每个线程内部使用局部变量来处理数据,只有在必要时才更新共享数据。
关键代码片段
use std::sync::{Arc, Mutex};
use std::thread;
struct ParallelProcessor {
shared_data: Arc<Mutex<Vec<i32>>>,
}
impl ParallelProcessor {
fn parallel_process(&self, num_threads: usize) -> Vec<i32> {
let mut handles = Vec::with_capacity(num_threads);
let data = self.shared_data.clone();
for _ in 0..num_threads {
let data_clone = data.clone();
let handle = thread::spawn(move || {
let mut data = data_clone.lock().unwrap();
// 这里进行具体的数据处理,例如对每个元素加1
for item in data.iter_mut() {
*item += 1;
}
// 返回处理后的数据副本(这里只是示例,实际可返回更有意义的结果)
data.clone()
});
handles.push(handle);
}
let mut results = Vec::new();
for handle in handles {
let result = handle.join().unwrap();
results.extend(result);
}
results
}
}
在上述代码中:
ParallelProcessor
结构体包含一个Arc<Mutex<Vec<i32>>>
类型的shared_data
,用于共享数据并保证线程安全。parallel_process
方法接收线程数量num_threads
,创建相应数量的线程来处理数据。- 每个线程通过
lock
获取Mutex
锁,对共享数据进行处理,处理完成后返回处理结果。 - 主线程通过
join
等待所有线程完成,并收集处理结果。
这样设计可以有效地处理并发带来的性能瓶颈,实现高效的并行处理。