面试题答案
一键面试可能出现的数据竞争场景
- 多个线程直接读写:
UnsafeCell
允许内部可变性,即可以绕过Rust的不可变借用规则。如果多个线程同时通过UnsafeCell
直接读写其包含的数据,就会产生数据竞争。例如:
use std::cell::UnsafeCell;
struct SharedData {
data: UnsafeCell<i32>,
}
let shared = SharedData { data: UnsafeCell::new(0) };
let threads: Vec<_> = (0..10).map(|_| {
let shared = &shared;
std::thread::spawn(move || {
let raw_ptr = shared.data.get();
unsafe {
*raw_ptr += 1;
}
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
在上述代码中,多个线程同时对UnsafeCell
中的i32
数据进行修改,没有任何同步机制,这就导致了数据竞争。
运用同步原语规避并发隐患
- 使用
Mutex
:Mutex
(互斥锁)可以确保同一时间只有一个线程能够访问被保护的数据。将UnsafeCell
包裹在Mutex
中,就可以控制对UnsafeCell
数据的访问。例如:
use std::cell::UnsafeCell;
use std::sync::{Arc, Mutex};
struct SharedData {
data: UnsafeCell<i32>,
}
let shared = Arc::new(Mutex::new(SharedData { data: UnsafeCell::new(0) }));
let threads: Vec<_> = (0..10).map(|_| {
let shared = shared.clone();
std::thread::spawn(move || {
let mut inner = shared.lock().unwrap();
let raw_ptr = inner.data.get();
unsafe {
*raw_ptr += 1;
}
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
这里通过Mutex
将SharedData
包裹,每个线程在访问UnsafeCell
中的数据前,先获取Mutex
的锁,这样就避免了数据竞争。
- 使用
RwLock
:如果对数据的读取操作远多于写入操作,可以使用RwLock
(读写锁)。它允许多个线程同时进行读操作,但只允许一个线程进行写操作。例如:
use std::cell::UnsafeCell;
use std::sync::{Arc, RwLock};
struct SharedData {
data: UnsafeCell<i32>,
}
let shared = Arc::new(RwLock::new(SharedData { data: UnsafeCell::new(0) }));
let threads: Vec<_> = (0..10).map(|_| {
let shared = shared.clone();
std::thread::spawn(move || {
let inner = shared.read().unwrap();
let raw_ptr = inner.data.get();
unsafe {
println!("Read data: {}", *raw_ptr);
}
})
}).collect();
for thread in threads {
thread.join().unwrap();
}
在这个例子中,多个线程可以同时读取UnsafeCell
中的数据,因为读操作是共享的。如果有写操作,需要获取写锁,这样就保证了写操作的原子性,避免了数据竞争。
通过使用Mutex
或RwLock
等同步原语包裹UnsafeCell
,可以有效地规避UnsafeCell
带来的并发隐患,确保多线程程序的安全性。