← 中文资源

简体中文版本

原子操作

原子操作不可分割地读写一个值,为计数器、标志与无锁数据结构提供基础构件,但无法让多个相关字段一起原子更新。

阅读英文原文

概念概述

原子操作(Atomic Operations)指的是:原子操作不可分割地读写一个值,为计数器、标志与无锁数据结构提供基础构件,但无法让多个相关字段一起原子更新。

从更严格的定义看,An atomic operation completes as an indivisible unit with respect to other threads, so no observer sees an intermediate state and no update is lost. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。

为什么需要它

It is used for counters, spin flags, reference counts, and the primitives on which larger synchronisation constructs are built. 它与 Seqlocks、Lock Fairness and Starvation、Coroutines 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。

常见误解

  • atomic operations are always faster than locks
  • an atomic read is enough when updating several related fields

需要避免的反模式

  • updating two atomics that must change together without a lock
  • choosing a relaxed memory ordering by default

实践要点

  1. Compare-and-swap loops let a value be updated optimistically and retried on contention, which is efficient at low contention but wasteful when many threads fight over one location.
  2. An operation on a single atomic does not make a group of related updates atomic, so invariants that span several variables still need a lock or a single packed state.
  3. Memory ordering parameters matter: relaxed ordering permits reordering, while acquire and release establish the visibility guarantees a caller may need.

参考来源

https://en.cppreference.com/w/cpp/atomic/atomic

代码示例

use arc_swap::ArcSwap;
use std::sync::Arc;

#[derive(Debug, Clone, PartialEq)]
struct Config {
    max_connections: usize,
    feature_flag: bool,
}

struct ConfigStore {
    current: ArcSwap<Config>,
}

impl ConfigStore {
    fn new(initial: Config) -> Self {
        Self {
            current: ArcSwap::from_pointee(initial),
        }
    }

    fn load(&self) -> Arc<Config> {
        self.current.load_full()
    }

    fn reload(&self, next: Config) -> Arc<Config> {
        self.current.swap(Arc::new(next))
    }
}

fn main() {
    let store = ConfigStore::new(Config {
        max_connections: 8,
        feature_flag: false,
    });

    let snapshot = store.load();
    let previous = store.reload(Config {
        max_connections: 16,
        feature_flag: true,
    });

    println!("previous: {:?}", previous);
    println!("snapshot: {:?}", snapshot);
    println!("current: {:?}", store.load());
    assert_eq!(store.load().max_connections, 16);
}
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0u64));
    let handles: Vec<_> = (0..8)
        .map(|_| {
            let counter = Arc::clone(&counter);
            thread::spawn(move || {
                for _ in 0..1000 {
                    let mut value = counter.lock().expect("mutex poisoned");
                    *value += 1;
                }
            })
        })
        .collect();

    for handle in handles {
        handle.join().expect("thread panicked");
    }
    let total = *counter.lock().expect("mutex poisoned");
    println!("total = {total}");
}