面试题答案
一键面试1. 原子操作
1.1 底层原理
原子操作是不可中断的操作,在多线程环境下能保证操作的完整性。Rust 中的 std::sync::atomic
模块提供了原子类型,如 AtomicI32
。这些类型通过硬件级别的支持,确保对它们的操作不会被其他线程干扰。例如,fetch_add
方法原子地增加一个值并返回旧值,在底层,现代 CPU 提供了特殊的指令(如 x86
架构上的 LOCK
前缀指令)来实现这种原子性。
1.2 代码示例
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
fn main() {
let counter = AtomicI32::new(0);
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
for _ in 0..1000 {
counter_clone.fetch_add(1, Ordering::SeqCst);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(counter.load(Ordering::SeqCst), 10 * 1000);
}
在这个示例中,多个线程通过 fetch_add
方法安全地对 AtomicI32
类型的 counter
进行操作,不会出现数据竞争。
2. 线程安全类型
2.1 底层原理
Rust 中有一些类型天生就是线程安全的,例如 Mutex<T>
和 RwLock<T>
。Mutex
(互斥锁)通过提供互斥访问来确保同一时间只有一个线程可以访问被保护的数据。当一个线程获取了 Mutex
的锁,其他线程必须等待锁被释放。RwLock
则区分了读锁和写锁,允许多个线程同时获取读锁,但写锁必须独占。
2.2 代码示例
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut handles = vec![];
for _ in 0..10 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut numbers = data_clone.lock().unwrap();
numbers.push(4);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let result = data.lock().unwrap();
assert_eq!(result.len(), 10 * 1 + 3);
}
这里通过 Mutex
保护了一个 Vec
,多个线程可以安全地修改这个 Vec
,不会出现数据竞争。
3. 通道(Channel)
3.1 底层原理
通道用于线程间的通信,std::sync::mpsc
模块提供了多生产者 - 单消费者(MPSC)通道。通道通过将数据从一个线程发送到另一个线程,避免了共享可变状态,从而防止数据竞争。当一个线程向通道发送数据时,数据的所有权被转移,接收线程成为数据的唯一所有者。
3.2 代码示例
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let mut handles = vec![];
for _ in 0..10 {
let tx_clone = tx.clone();
let handle = thread::spawn(move || {
tx_clone.send(42).unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
for _ in 0..10 {
let value = rx.recv().unwrap();
assert_eq!(value, 42);
}
}
在这个示例中,多个线程通过通道发送数据,避免了共享数据带来的数据竞争。
4. 综合运用
use std::sync::{Arc, Mutex, mpsc};
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
fn main() {
let counter = AtomicI32::new(0);
let data = Arc::new(Mutex::new(vec![]));
let (tx, rx) = mpsc::channel();
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let data_clone = Arc::clone(&data);
let tx_clone = tx.clone();
let handle = thread::spawn(move || {
counter_clone.fetch_add(1, Ordering::SeqCst);
let mut numbers = data_clone.lock().unwrap();
numbers.push(counter_clone.load(Ordering::SeqCst));
tx_clone.send(numbers.clone()).unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let mut all_numbers = vec![];
for _ in 0..10 {
let numbers = rx.recv().unwrap();
all_numbers.extend(numbers);
}
assert_eq!(all_numbers.len(), 10);
assert_eq!(counter.load(Ordering::SeqCst), 10);
}
在这个复杂示例中,同时使用了原子操作(AtomicI32
)来计数,Mutex
保护共享数据 Vec
,以及通道来安全地在多线程间传递数据,综合运用这些策略确保了程序在并发情况下的数据一致性和安全性。