面试题答案
一键面试资源竞争问题分析
在多线程环境下频繁读写共享资源,可能出现以下资源竞争问题:
- 数据不一致:多个线程同时写共享资源,或者一个线程写、另一个线程读,可能导致读到的数据是未完整写入的,从而出现数据不一致的情况。例如,一个线程对共享的计数器进行加1操作,但在读取当前值和写入新值之间,另一个线程也读取了该值并进行操作,就会造成数据错误。
- 死锁:当多个线程互相等待对方释放资源时,就会产生死锁。比如线程A持有资源1并等待资源2,而线程B持有资源2并等待资源1,这两个线程就会永远阻塞下去。
使用Rust机制解决问题
- Mutex(互斥锁):
- 原理:Mutex提供了一种独占访问共享资源的机制。只有获取到锁的线程才能访问共享资源,其他线程必须等待锁的释放。
- 示例代码:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data_clone = data.clone();
let handle = thread::spawn(move || {
let mut num = data_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", *data.lock().unwrap());
}
- 优化点:虽然Mutex能保证数据一致性,但在高并发读场景下,会造成不必要的等待开销。因为读操作之间并不会互相影响数据一致性。
- RwLock(读写锁):
- 原理:RwLock区分了读锁和写锁。多个线程可以同时获取读锁来读取共享资源,因为读操作不会改变数据。只有当需要写操作时,才需要获取写锁,此时其他线程无论是读还是写都要等待。
- 示例代码:
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data_clone = data.clone();
let handle = thread::spawn(move || {
let num = data_clone.read().unwrap();
println!("Read value: {}", *num);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let mut write_guard = data.write().unwrap();
*write_guard += 1;
println!("Final value after write: {}", *write_guard);
}
- 优化点:在读写频繁但写操作相对较少的场景下,RwLock能显著减少CPU执行时间的等待开销。读操作可以并行进行,只有写操作会阻塞其他读写操作。
进一步优化
- 减少锁的粒度:尽量缩小持有锁的代码块范围,只在真正需要访问共享资源时获取锁,访问结束后尽快释放锁。这样可以减少其他线程等待锁的时间。
- 使用无锁数据结构:对于一些简单的共享数据操作场景,如原子计数器,可以使用Rust的
std::sync::atomic
模块中的原子类型。这些类型提供了无锁的原子操作,避免了锁带来的开销。例如AtomicU32
可以在多线程环境下安全地进行自增等操作。
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 || {
counter_clone.fetch_add(1, Ordering::SeqCst);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", counter.load(Ordering::SeqCst));
}