跳到主要内容

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 配对写
AcqRelAcquire + 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::SeqCstOrdering::Relaxed 的性能差距大吗?

答案

在 x86 架构上差距很小(x86 本身提供较强的内存顺序保证)。在 ARM/RISC-V 等弱内存序架构上差距较大。

如果不确定该用哪个,用 SeqCst 最安全。只有在性能分析显示是瓶颈时才考虑降级。

Q4: Arc<Mutex<T>> 可以用 Arc<RwLock<T>> 代替吗?

答案

可以,但需要根据读写比例选择。如果主要是读操作,RwLock 允许并发读,吞吐量更高。如果读写均衡,Mutex 更简单高效(RwLock 有写者饥饿问题)。

相关链接