原子
原文链接: https://marabos.nl/atomics/atomics.html.
英文: Atomic.
原子 (Atomic) 一词源于希腊语 ἄτομος, 意为不可再分的.
所有其他并发原语 (例如互斥体和条件变量) 都是使用原子操作实现的.
Rust 提供多种原子类型 (std::sync::atomic
), 但并非所有平台都支持所有的原子类型.
Load 和 Store 操作
原子类型最基础的操作是 load
和 store
, 对应读和写操作.
以 AtomicI32 的函数签名为例:
impl AtomicI32 {
pub fn load(&self, order: Ordering) -> i32;
pub fn store(&self, val: i32, order: Ordering);
}
文中举了一个名为 "Stop Flag" 的例子, 展示了仅通过 load
和 store
操作就能实现的功能.
该例子中, 原子变量只在一个线程里被读取 (load
), 在另一个线程里被写入 (store
), 所以不可能导致冲突.
Fetch-and-Modify 操作
但如果需要基于原子变量的值进行修改, 只使用 load
和 store
操作, 就可能导致竞争1.
原子类型提供多种 Fetch-and-Modify 操作, 比如用于加法操作的 fetch_add
. 可以用于对原子类型做简单的算术运算, 同时保持原子性.
以 AtomicI32 的函数签名为例:
impl AtomicI32 {
pub fn fetch_add(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_sub(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_or(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_and(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_nand(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_xor(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_max(&self, val: i32, order: Ordering) -> i32;
pub fn fetch_min(&self, val: i32, order: Ordering) -> i32;
pub fn swap(&self, val: i32, order: Ordering) -> i32; // "fetch_store"
}
Compare-and-Exchange 操作
最灵活和通用的操作是 compare_exchange
, 该操作也是其他 Fetch-and-Modify 操作的实现基础.
以 AtomicI32 的函数签名为例:
impl AtomicI32 {
pub fn compare_exchange(
&self,
current: i32,
new: i32,
success: Ordering,
failure: Ordering,
) -> Result<i32, i32>;
}
该方法是一个原子操作 (对应 CAS (compare-and-swap) 原子指令), 若原子变量的值等于 current
, 将原子变量的值改为 new
.
交换 (exchange 或 swap) 主要体现在该方法会返回原子变量原本的值, 虽然这个值理论上等于 current
.
下面是 fetch_add
的一种可能的实现:
impl AtomicI32 {
pub fn fetch_add(&self, val: i32, order: Ordering) -> i32 {
let mut current = self.load(Relaxed);
loop {
let new = current + val;
match self.compare_exchange(current, new, Relaxed, Relaxed) {
Ok(v) => return v,
Err(v) => current = v,
}
}
}
}
上文仅通过 load
和 store
操作实现了例子 "Lazy Initialization", 但这种实现方式存在以下限制:
- 初始化函数的返回值必须是恒定的: 这样才能在有竞争的情况下, 保证中间和最终结果是正确的.
- 可能调用多次初始化函数: 会产生不必要的性能开销.
通过 compare_exchange
操作, 可以实现例子 "Lazy One-Time Initialization". 与原先的例子相比, 该方法不存在上述的第一条限制.
如果还需要消除第二条限制, 则需要堵塞线程, 避免多次调用初始化函数. Rust 标准库通过以下结构体提供该功能:
std::sync::Once
: 确保一段代码只被执行一次. 如果这段代码用于初始化某个静态可变变量, 则使用者需编写 unsafe 代码.std::sync::OnceLock
: 确保一个变量只被初始化一次. 相比std::sync::Once
, 其目的更明确, 只用于初始化变量, 所以封装了 unsafe 代码, 使用者无需编写 unsafe 代码.
-
是竞争 (race) 而非数据竞争 (data race), 数据竞争是未定义行为, 不会出现在 safe Rust 中. ↩