Arc 与原子类型
问题
Arc 和原子类型在 Rust 并发中扮演什么角色?内存顺序有哪些?
答案
Arc(Atomic Reference Counting)
Arc<T> 是线程安全的引用计数智能指针,允许多个线程共享同一份数据的所有权:
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
for i in 0..3 {
let data = Arc::clone(&data); // 增加引用计数(原子操作)
handles.push(thread::spawn(move || {
println!("线程 {} 读取: {:?}", i, data);
}));
}
for h in handles { h.join().unwrap(); }
// 所有 Arc 被 drop 后,数据才被释放
| 特性 | Rc<T> | Arc<T> |
|---|---|---|
| 引用计数操作 | 非原子 | 原子 |
| Send | ❌ | ✅ |
| Sync | ❌ | ✅ |
| 性能 | 更快 | 原子操作开销 |
| 使用场景 | 单线程 | 多线程 |
Arc + Mutex 组合
Arc 只提供共享所有权,不提供可变性。配合 Mutex 实现可变共享:
use std::sync::{Arc, Mutex};
let shared = Arc::new(Mutex::new(HashMap::new()));
let writer = {
let shared = shared.clone();
thread::spawn(move || {
shared.lock().unwrap().insert("key", "value");
})
};
let reader = {
let shared = shared.clone();
thread::spawn(move || {
println!("{:?}", shared.lock().unwrap().get("key"));
})
};
原子类型(Atomics)
适用于简单的无锁并发操作:
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
static RUNNING: AtomicBool = AtomicBool::new(true);
fn main() {
let mut handles = vec![];
for _ in 0..10 {
handles.push(thread::spawn(|| {
for _ in 0..1000 {
COUNTER.fetch_add(1, Ordering::Relaxed);
}
}));
}
for h in handles { h.join().unwrap(); }
println!("计数: {}", COUNTER.load(Ordering::Relaxed)); // 10000
}
内存顺序(Memory Ordering)
| 顺序 | 保证 | 性能 | 使用场景 |
|---|---|---|---|
Relaxed | 只保证原子性 | 最快 | 计数器 |
Acquire | 之后的读不会重排到之前 | 中等 | 与 Release 配对读 |
Release | 之前的写不会重排到之后 | 中等 | 与 Acquire 配对写 |
AcqRel | Acquire + Release | 中等 | CAS 操作 |
SeqCst | 全局顺序一致 | 最慢 | 不确定时的安全选择 |
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
static DATA: AtomicU64 = AtomicU64::new(0);
static READY: AtomicBool = AtomicBool::new(false);
// 生产者
fn producer() {
DATA.store(42, Ordering::Relaxed);
READY.store(true, Ordering::Release); // Release:之前的写对其他线程可见
}
// 消费者
fn consumer() {
while !READY.load(Ordering::Acquire) {} // Acquire:看到 Release 之前的所有写
assert_eq!(DATA.load(Ordering::Relaxed), 42); // ✅ 一定是 42
}
常见面试问题
Q1: Arc::clone 的开销大吗?
答案:
Arc::clone 只是原子加一操作(fetch_add),非常快(通常几纳秒)。不会拷贝底层数据。但频繁 clone/drop Arc 可能导致缓存行争用,在极端高并发场景需要注意。
Q2: 什么时候用原子类型代替 Mutex?
答案:
- 原子类型:简单数值的读写(计数器、标志位、状态标记)
- Mutex:复杂数据结构、多步操作需要保持一致性
原子类型无锁且性能高,但只能处理单个值的操作。如果需要同时修改多个字段,必须用 Mutex。
Q3: Ordering::SeqCst 和 Ordering::Relaxed 的性能差距大吗?
答案:
在 x86 架构上差距很小(x86 本身提供较强的内存顺序保证)。在 ARM/RISC-V 等弱内存序架构上差距较大。
如果不确定该用哪个,用 SeqCst 最安全。只有在性能分析显示是瓶颈时才考虑降级。
Q4: Arc<Mutex<T>> 可以用 Arc<RwLock<T>> 代替吗?
答案:
可以,但需要根据读写比例选择。如果主要是读操作,RwLock 允许并发读,吞吐量更高。如果读写均衡,Mutex 更简单高效(RwLock 有写者饥饿问题)。