面试题答案
一键面试AtomicUsize
与普通 usize
的区别
- 线程安全性:
AtomicUsize
:是线程安全的。它提供了原子操作,确保对该类型数据的修改在多线程环境下不会出现数据竞争。例如,fetch_add
方法可以原子地增加AtomicUsize
的值。usize
:不是线程安全的。在多线程环境下直接对usize
进行操作,可能会导致数据竞争,因为多个线程同时读写usize
时,其操作不是原子的。
- 适用场景:
AtomicUsize
:适用于简单的、需要原子操作的计数器或标志位场景。例如,在多线程环境下统计某些事件发生的次数,并且每次统计操作都是简单的原子增加或减少。usize
:适用于单线程环境,或者在多线程环境下通过其他机制(如Mutex
)来保证线程安全的场景。如果对数据的操作比较复杂,不只是简单的原子操作,使用Mutex
包裹usize
可能更合适。
使用并发原语安全共享和操作原生类型数据
- 使用
Mutex
包裹原生类型:Mutex
(互斥锁)可以用来保护普通的原生类型,如usize
。通过Mutex
获得锁后,才能对内部包裹的原生类型进行操作,从而避免数据竞争。- 示例代码:
use std::sync::{Mutex, Arc}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Final counter value: {}", *counter.lock().unwrap()); }
- 使用
AtomicUsize
直接操作:AtomicUsize
提供了原子操作方法,无需像Mutex
那样通过锁机制。- 示例代码:
use std::sync::{Arc, atomic::{AtomicUsize, Ordering}}; use std::thread; fn main() { let counter = Arc::new(AtomicUsize::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { counter.fetch_add(1, Ordering::SeqCst); }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Final counter value: {}", counter.load(Ordering::SeqCst)); }
性能和线程安全的多线程计数器设计
- 简单场景下使用
AtomicUsize
:- 如果计数器操作主要是简单的增减操作,
AtomicUsize
性能更好,因为它无需获取锁,直接进行原子操作。 - 代码实现如上述
AtomicUsize
示例。
- 如果计数器操作主要是简单的增减操作,
- 复杂场景下使用
Mutex
包裹usize
:- 如果除了简单的增减操作,还需要对计数器进行复杂的逻辑操作(如根据计数器的值进行不同的分支处理),使用
Mutex
包裹usize
更合适。虽然获取锁会带来一定性能开销,但能保证复杂操作的线程安全性。 - 代码实现如上述
Mutex
示例,可在获取锁后对usize
进行复杂操作。
- 如果除了简单的增减操作,还需要对计数器进行复杂的逻辑操作(如根据计数器的值进行不同的分支处理),使用