前置知识: C++

内存序与无锁编程

55 minAdvanced2026/7/20

C++内存序与无锁编程详解:std::memory_order、std::atomic、fence、ABA 问题与无锁数据结构。

内存序与无锁编程

本文档系统讲解 C++11 引入的内存模型与原子操作、6 种内存序(relaxed / consume / acquire / release / acq_rel / seq_cst)、内存屏障、ABA 问题、无锁队列、缓存行与 false sharing 等核心主题。所有代码示例可在支持 C++17/20/23 的主流编译器上编译通过,标注 x86/ARM/POWER 架构差异。对标 MIT 6.172、Stanford CS149、CMU 15-440 课程教学水准。

1. 学习目标

完成本章学习后,读者应能够达成以下 Bloom 认知层级目标:

Bloom 层级目标描述
Remember(记忆)列出 6 种 std::memory_order 的名称与基本语义,复述 happens-before 与 synchronizes-with 关系的定义
Understand(理解)解释 x86 TSO 内存模型与 ARM/POWER relaxed 内存模型的差异,说明 acquire-release 配对的工作原理
Apply(应用)使用 std::atomic 实现自旋锁、生产者-消费者队列,正确选择内存序以平衡正确性与性能
Analyze(分析)分析给定代码片段的内存序是否过度严格或过于宽松,识别潜在数据竞争与 UB
Evaluate(评价)评估无锁数据结构的性能特性(吞吐、延迟、可扩展性),权衡无锁 vs 互斥方案的取舍
Create(创造)设计并实现无锁队列、无锁栈、无锁 hashmap,处理 ABA 问题与内存回收

2. 历史动机与发展脉络

2.1 多核时代的内存模型挑战

单核时代,编译器与 CPU 通过顺序一致性(sequential consistency, SC)抽象隐藏了内存访问的复杂性。但多核系统中:

  • CPU 缓存层级:L1/L2/L3 多级缓存导致核心间数据视图不一致;
  • 写缓冲(Store Buffer):x86 引入写缓冲导致 store-load 重排;
  • 乱序执行(Out-of-Order Execution):CPU 动态调度指令以提升 IPC;
  • 编译器优化:编译器在单线程视角下可自由重排指令。

例如,以下代码在弱内存模型架构上可能输出 y=0, z=0(Peterson 算法失效):

// 线程 1          // 线程 2
x = 1;             y = 1;
r1 = y;            r2 = x;
// 期望 r1=1 或 r2=1,但 ARM/POWER 上可能 r1=0, r2=0

2.2 C++11 内存模型的引入

C++11 标准首次为 C++ 定义了形式化的内存模型(memory model),见 ISO/IEC 14882:2011 §1.10。关键提案:

  • N2007 Memory Model for C++(Hans-J. Boehm, 2005):奠基性提案,定义 happens-before 关系。
  • N2052 A Primer on Synchronization and Sequential Consistency(Boehm, 2006):澄清 SC 与数据竞争。
  • N2145 Atomic Operations in C++(Lawrence Crowl, 2007):原子操作 API 设计。
  • N2348 A Strong and Safe Memory Model for C++(Boehm, 2007):最终版本。
  • N2427 Atomics in the C++ Standard(Crowl, 2007):API 细节。
  • N2752 Concurrency in C++(Lawrence Crowl, 2008):完整并发章节。

C++11 模型基于 Java Memory Model(JSR-133)改良,但去掉了 Java 的 volatile 语义与 final field 规则,更接近硬件内存模型。

2.3 C++14/17/20/23/26 演进

标准关键变化文档编号
C++11引入 std::atomic、6 种 std::memory_orderstd::atomic_thread_fencestd::atomic_signal_fencestd::mutex 系列N2348, N2752
C++14std::shared_timed_mutex 用于读写锁;constexpr 原子操作N3659
C++17std::atomic_ref<T> 对已有对象原子访问;std::scoped_lock 多锁获取P0019, P0156
C++20std::atomic_ref 完善;std::counting_semaphorestd::latchstd::barrierstd::atomic 扩展到 std::shared_ptrP1135, P0718
C++23std::atomic_ref<T>::required_alignmentstd::flat_map 等容器与原子协作;std::move_only_function 与原子P1889
C++26Hazard pointer(P2530)、std::rcu(P2545)、std::atomic_ref 扩展到 floating-point(P2045);std::execution 与无锁并发整合(草案)P2530, P2545

2.4 与其他语言的横向对比

特性C++JavaRustGoC#
内存模型形式化C++11JMM (JSR-133)借鉴 C++ 与 JMMGo MMECMA-335
原子操作 APIstd::atomic<T>VarHandle/AtomicXxxstd::sync::atomicsync/atomicInterlocked/Volatile
内存序选项6 种volatilefinalhappens-before5 种(无 consume)LoadSeqCst 等 3 种4 种
无锁数据结构库第三方(boost::lockfree)java.util.concurrentcrossbeamsync/atomic + 自实现System.Collections.Concurrent
cache line paddingalignas(64)@Contended#[repr(align(64))]//go:noescape + 自定义[StructLayout]

3. 形式化定义

3.1 C++ 内存模型基础

C++ 内存模型将程序视为对抽象机器上内存位置的访问序列。形式化定义如下(ISO/IEC 14882:2023 §6.9.2):

  • 内存位置(memory location):标量对象(如 intchar)或相邻位域组成的最大序列。
  • 求值(evaluation):包括 value computation(值计算)和 side effect(副作用,如写入内存)。
  • 数据竞争(data race):两个线程对同一内存位置进行至少一个写操作,且无 happens-before 关系时,构成数据竞争,导致 UB。

3.2 Happens-before 关系

形式化定义 happens-before 关系 hb\xrightarrow{hb}

AhbB    {A sequenced-before B(同一线程内的顺序关系)A synchronizes-with B(跨线程同步关系)AhbCChbB(传递闭包)A \xrightarrow{hb} B \iff \begin{cases} A \text{ sequenced-before } B & \text{(同一线程内的顺序关系)} \\ A \text{ synchronizes-with } B & \text{(跨线程同步关系)} \\ A \xrightarrow{hb} C \wedge C \xrightarrow{hb} B & \text{(传递闭包)} \end{cases}
  • sequenced-before:单线程内,依据语言规则确定的求值顺序。
  • synchronizes-with:跨线程同步关系,通常由原子操作或 fence 建立。

3.3 6 种内存序的形式化语义

std::memory_order 枚举定义于 <atomic>,6 个值的语义如下:

枚举值语义同步保证
memory_order_relaxed仅保证原子性,不提供顺序保证
memory_order_consume当前线程内依赖该 load 的操作不能重排到 load 之前数据依赖同步
memory_order_acquire当前线程内后续的读/写不能重排到 load 之前与 release 配对
memory_order_release当前线程内之前的读/写不能重排到 store 之后与 acquire/consume 配对
memory_order_acq_relload 与 store 都有相应保证acquire + release
memory_order_seq_cst全局总序,所有线程观察到的顺序一致最强

形式化表达:

  • relaxedatomic_rw(x,relaxed)\text{atomic\_rw}(x, \text{relaxed}) 仅保证原子性,不建立 synchronizes-with 关系。
  • acquire/release:若 AArelease store 且 BBacquire load 且读取了 AA 写入的值,则 AswBA \xrightarrow{sw} B,进一步推导出 AhbBA \xrightarrow{hb} B
  • seq_cst:在 acquire/release 基础上,附加存在单一全局总序 SS,所有 seq_cst 操作按 SS 顺序被所有线程观察到。

3.4 synchronizes-with 关系建立

synchronizes-with sw\xrightarrow{sw} 关系通过以下机制建立:

  1. Release-Acquire 配对

    • 线程 A 执行 x.store(v, release)
    • 线程 B 执行 x.load(acquire) 且读取到值 vv
    • 则 A 的 release 之前的所有写操作对 B 的 acquire 之后的所有读/写操作可见。
  2. Release-Consume 配对(C++17 起 deprecated,C++23 重新规范):

    • 仅同步依赖该 load 的操作。
  3. Fence-Fence 配对

    • A 侧 atomic_thread_fence(release) + B 侧 atomic_thread_fence(acquire) + 共享原子变量。
  4. Seq_cst 全序

    • 所有 seq_cst 操作构成单一全局序 SS,所有线程观察一致。

3.5 内存屏障的形式化

std::atomic_thread_fence(order) 引入的屏障对指令重排的影响:

order屏障语义
relaxed无屏障作用
acquire屏障后的读/写不能重排到屏障前
release屏障前的读/写不能重排到屏障后
acq_relacquire + release
seq_cstacquire + release + 全局总序

3.6 形式化示例:单生产者-单消费者

std::atomic<bool> ready{false};
int data = 0;

// 线程 A(生产者)
void producer() {
    data = 42;                              // (1) 普通写
    ready.store(true, std::memory_order_release);  // (2) release store
}

// 线程 B(消费者)
void consumer() {
    while (!ready.load(std::memory_order_acquire)) {}  // (3) acquire load
    assert(data == 42);                     // (4) 读取,保证看到 42
}

形式化推导:

  1. (1) sequenced-before (2)(同线程顺序);
  2. (2) synchronizes-with (3)(release-acquire 配对,B 读到 true);
  3. (3) sequenced-before (4)(同线程顺序);
  4. 由传递闭包:(1) happens-before (4)
  5. (4) 读取 data 时保证看到 (1) 写入的 42。

4. 理论推导与原理解析

4.1 为什么需要内存序

考虑以下经典示例(“消息发布”模式):

std::atomic<int> flag{0};
int message = 0;

// 线程 A
message = 42;
flag.store(1, std::memory_order_relaxed);

// 线程 B
while (flag.load(std::memory_order_relaxed) != 1);
print(message);  // 期望 42,但可能输出 0!

问题分析memory_order_relaxed 不建立 happens-before 关系,CPU 与编译器可重排指令。在弱内存模型架构(ARM、POWER)上,线程 A 的 message = 42 可能被重排到 flag.store 之后,导致线程 B 看到 flag=1 但 message 仍为 0。

修复:使用 release-acquire 配对:

// 线程 A
message = 42;
flag.store(1, std::memory_order_release);  // release

// 线程 B
while (flag.load(std::memory_order_acquire) != 1);  // acquire
print(message);  // 保证看到 42

4.2 CPU 内存模型对比

4.2.1 x86-TSO(Total Store Order)

x86 采用 TSO 模型,比 SC 弱化一点点:

  • 允许 store-load 重排(写缓冲导致);
  • 不允许 store-store、load-load、load-store 重排。

形式化:x86 程序在 SC 语义下,仅当出现 store-load 序对时可能观察不一致。

# x86-TSO 允许的重排
mov [x], 1   ; store
mov eax, [y] ; load
# 可能被重排为:先 load [y],再 store [x]

4.2.2 ARM/POWER(Relaxed Memory Model)

ARM 与 POWER 是更弱的内存模型:

  • 允许 store-store、load-load、store-load、load-store 全部重排;
  • 需要显式屏障指令(dmb on ARM, lwsync/sync on POWER)。
# ARM relaxed model
str r1, [x]   ; store
ldr r2, [y]   ; load
# 无屏障时可能任意重排
dmb ish       ; 数据内存屏障(inner shareable)

4.2.3 内存序在不同架构上的成本

内存序x86 编译为ARM 编译为成本(相对)
relaxed普通 mov普通 ldr/str1x
acquire(load)普通 movldr + dmb ishldx86: 1x; ARM: 2-3x
release(store)普通 movdmb ish + strx86: 1x; ARM: 2-3x
seq_cst(load)mov + mfencelock xaddldr + dmb ishx86: 5-10x; ARM: 2-3x
seq_cst(store)xchgmov + mfencedmb ish + str + dmb ishx86: 5-10x; ARM: 3-5x

关键洞察:在 x86 上,acquire/release 几乎免费,但 seq_cst 显著更贵。在 ARM 上,所有非 relaxed 内存序都有显著开销,但仍比 seq_cst 便宜。

4.3 CAS(Compare-Exchange-Swap)原理

compare_exchange_weak/strong 是原子操作的核心原语。其语义:

bool compare_exchange(expected, desired):
    if (*this == expected) {
        *this = desired;
        return true;
    } else {
        expected = *this;
        return false;
    }

形式化:

CASorder(ptr,expected,desired)={trueif ptr=expected,and ptrdesiredfalseotherwise, expectedptr\text{CAS}_{order}(\text{ptr}, \text{expected}, \text{desired}) = \begin{cases} \text{true} & \text{if } *\text{ptr} = \text{expected}, \text{and } *\text{ptr} \leftarrow \text{desired} \\ \text{false} & \text{otherwise, expected} \leftarrow *\text{ptr} \end{cases}

CAS 支持 “双内存序”:成功时使用 success_order,失败时使用 failure_order(failure_order 不能强于 success_order)。

std::atomic<int> a{0};
int expected = 0;
bool ok = a.compare_exchange_strong(
    expected, 1,
    std::memory_order_acq_rel,  // 成功时
    std::memory_order_acquire   // 失败时
);

4.4 ABA 问题

CAS 检查值相等即认为 “无变化”,但值可能经历过 A→B→A 的变化:

// 线程 1 读到 head = A,准备 CAS(A, B)
// 线程 2 pop A, push B, push A(A 被复用)
// 线程 1 CAS(A, B) 成功,但中间状态已被破坏

解决方案

  1. 带版本号的指针std::atomic<uintptr_t> 高位存版本,低位存指针;
  2. std::atomic<std::shared_ptr>(C++20):自动管理引用计数,避免 ABA;
  3. Hazard pointer(C++26 草案 P2530):线程持引用期间防止回收;
  4. Epoch-based reclamation:分代回收,类似 RCU;
  5. std::rcu(C++26 草案 P2545):RCU 机制标准化。

4.5 缓存行与 False Sharing

CPU 缓存以缓存行(cache line)为单位(通常 64 字节)。若两个线程频繁修改位于同一缓存行的不同变量,会导致缓存行在核心间频繁迁移,性能严重退化。

struct Bad {
    std::atomic<int> a;  // 线程 1 频繁写
    std::atomic<int> b;  // 线程 2 频繁写
};  // a 与 b 大概率在同一缓存行 → false sharing

解决方案:使用 alignas(64) 对齐到缓存行:

struct Good {
    alignas(64) std::atomic<int> a;
    alignas(64) std::atomic<int> b;
};  // a 与 b 在不同缓存行,无 false sharing

4.6 公平性与无锁性等级

按 Herlihy-Shavit 分类:

  • wait-free:每个操作在有限步内完成(最强);
  • lock-free:至少一个线程在有限步内完成进度;
  • obstruction-free:单线程独占执行时可在有限步内完成;
  • blocking:使用互斥锁,被抢占则阻塞。

形式化:

progress(system)={wait-freetthreads,t always progresseslock-freetthreads,t progressesobstruction-freeisolated t progressesblockingotherwise\text{progress}(\text{system}) = \begin{cases} \text{wait-free} & \forall t \in \text{threads}, t \text{ always progresses} \\ \text{lock-free} & \exists t \in \text{threads}, t \text{ progresses} \\ \text{obstruction-free} & \text{isolated } t \text{ progresses} \\ \text{blocking} & \text{otherwise} \end{cases}

5. 代码示例(企业级 production-ready)

5.1 自旋锁实现

// file: spinlock.cpp
// compile: g++ -std=c++17 -O2 -pthread -o spinlock spinlock.cpp
#include <atomic>
#include <thread>
#include <iostream>
#include <chrono>

class SpinLock {
    std::atomic<bool> locked_{false};

public:
    void lock() {
        // 第一阶段:自旋等待
        while (locked_.exchange(true, std::memory_order_acquire)) {
            // 第二阶段:使用 pause 减少功耗(x86)
#if defined(__x86_64__) || defined(_M_X64)
            __builtin_ia32_pause();
#endif
        }
    }

    void unlock() {
        locked_.store(false, std::memory_order_release);
    }

    bool try_lock() {
        return !locked_.exchange(true, std::memory_order_acquire);
    }
};

int main() {
    SpinLock lock;
    int counter = 0;

    auto work = [&]() {
        for (int i = 0; i < 1'000'000; ++i) {
            std::lock_guard<SpinLock> lk(lock);
            ++counter;
        }
    };

    std::thread t1(work);
    std::thread t2(work);
    t1.join();
    t2.join();

    std::cout << "counter = " << counter << "\n";  // 期望 2000000
    return 0;
}

5.2 生产者-消费者(无锁 SPSC 队列)

// file: spsc_queue.cpp
// compile: g++ -std=c++17 -O2 -pthread -o spsc spsc_queue.cpp
#include <atomic>
#include <cstddef>
#include <new>
#include <thread>
#include <iostream>

template <typename T, size_t Capacity>
class SPSCQueue {
    alignas(64) std::atomic<size_t> write_pos_{0};
    alignas(64) std::atomic<size_t> read_pos_{0};
    alignas(64) T buffer_[Capacity];

public:
    bool push(const T& value) {
        size_t wp = write_pos_.load(std::memory_order_relaxed);
        size_t rp = read_pos_.load(std::memory_order_acquire);
        if (wp - rp >= Capacity) return false;  // 满

        buffer_[wp % Capacity] = value;
        write_pos_.store(wp + 1, std::memory_order_release);
        return true;
    }

    bool pop(T& out) {
        size_t rp = read_pos_.load(std::memory_order_relaxed);
        size_t wp = write_pos_.load(std::memory_order_acquire);
        if (rp == wp) return false;  // 空

        out = buffer_[rp % Capacity];
        read_pos_.store(rp + 1, std::memory_order_release);
        return true;
    }
};

int main() {
    SPSCQueue<int, 1024> queue;
    constexpr int N = 1'000'000;
    int sum = 0;

    std::thread producer([&]() {
        for (int i = 0; i < N; ++i) {
            while (!queue.push(i)) std::this_thread::yield();
        }
    });

    std::thread consumer([&]() {
        for (int i = 0; i < N; ++i) {
            int v;
            while (!queue.pop(v)) std::this_thread::yield();
            sum += v;
        }
    });

    producer.join();
    consumer.join();

    std::cout << "sum = " << sum << "\n";  // 期望 499999500000
    return 0;
}

5.3 无锁栈(带 ABA 解决方案)

// file: lockfree_stack.cpp
// compile: g++ -std=c++20 -O2 -pthread -o lfstack lockfree_stack.cpp
#include <atomic>
#include <memory>
#include <iostream>
#include <thread>

template <typename T>
class LockFreeStack {
    struct Node {
        std::shared_ptr<T> data;
        Node* next;
        Node(T x) : data(std::make_shared<T>(std::move(x))), next(nullptr) {}
    };

    std::atomic<Node*> head_{nullptr};

public:
    void push(T value) {
        Node* new_node = new Node(std::move(value));
        new_node->next = head_.load(std::memory_order_relaxed);
        while (!head_.compare_exchange_weak(
            new_node->next, new_node,
            std::memory_order_release,
            std::memory_order_relaxed)) {
            // CAS 失败时 new_node->next 自动更新
        }
    }

    std::shared_ptr<T> pop() {
        Node* old_head = head_.load(std::memory_order_relaxed);
        while (old_head &&
               !head_.compare_exchange_weak(
                   old_head, old_head->next,
                   std::memory_order_acquire,
                   std::memory_order_relaxed)) {
            // CAS 失败时 old_head 自动更新
        }
        if (!old_head) return nullptr;
        std::shared_ptr<T> result = old_head->data;
        // 注意:直接 delete old_head 可能引发 ABA
        // 生产环境应使用 hazard pointer 或 RCU
        delete old_head;
        return result;
    }

    ~LockFreeStack() {
        while (pop()) {}
    }
};

int main() {
    LockFreeStack<int> stack;
    constexpr int N = 100'000;

    std::thread t1([&]() { for (int i = 0; i < N; ++i) stack.push(i); });
    std::thread t2([&]() { for (int i = 0; i < N; ++i) stack.push(i + N); });

    t1.join();
    t2.join();

    int count = 0;
    while (stack.pop()) ++count;
    std::cout << "popped: " << count << "\n";  // 期望 200000
    return 0;
}

5.4 双检查锁定(Double-Checked Locking)

// file: dclp.cpp
// compile: g++ -std=c++17 -O2 -pthread -o dclp dclp.cpp
#include <atomic>
#include <mutex>
#include <iostream>
#include <memory>

class Singleton {
    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
    int data_;

    Singleton() : data_(42) {
        std::cout << "Singleton constructed\n";
    }

public:
    static Singleton* getInstance() {
        Singleton* p = instance_.load(std::memory_order_acquire);
        if (!p) {
            std::lock_guard<std::mutex> lk(mutex_);
            p = instance_.load(std::memory_order_relaxed);
            if (!p) {
                p = new Singleton();
                instance_.store(p, std::memory_order_release);
            }
        }
        return p;
    }

    int getData() const { return data_; }
};

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;

int main() {
    auto* s1 = Singleton::getInstance();
    auto* s2 = Singleton::getInstance();
    std::cout << "s1 == s2: " << (s1 == s2) << "\n";
    std::cout << "data: " << s1->getData() << "\n";
    return 0;
}

5.5 false sharing 演示与修复

// file: false_sharing.cpp
// compile: g++ -std=c++17 -O2 -pthread -o fsh false_sharing.cpp
#include <atomic>
#include <thread>
#include <chrono>
#include <iostream>
#include <cstdint>

// 错误版本:两个计数器在同一缓存行
struct BadCounter {
    std::atomic<uint64_t> a{0};
    std::atomic<uint64_t> b{0};
};

// 修复版本:使用 alignas 隔离到不同缓存行
struct alignas(64) GoodCounter {
    std::atomic<uint64_t> a{0};
    std::atomic<uint64_t> b{0};
};

template <typename Counter>
void benchmark(const char* name) {
    Counter c;
    constexpr int N = 100'000'000;

    auto t0 = std::chrono::steady_clock::now();
    std::thread t1([&]() { for (int i = 0; i < N; ++i) c.a.fetch_add(1, std::memory_order_relaxed); });
    std::thread t2([&]() { for (int i = 0; i < N; ++i) c.b.fetch_add(1, std::memory_order_relaxed); });
    t1.join();
    t2.join();
    auto t1_end = std::chrono::steady_clock::now();

    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1_end - t0).count();
    std::cout << name << ": " << ms << " ms, a=" << c.a.load() << " b=" << c.b.load() << "\n";
}

int main() {
    benchmark<BadCounter>("BadCounter (false sharing)");
    benchmark<GoodCounter>("GoodCounter (cache aligned)");
    return 0;
}

典型输出(x86-64, 4 cores):

BadCounter (false sharing): 1247 ms, a=100000000 b=100000000
GoodCounter (cache aligned): 412 ms, a=100000000 b=100000000

修复后性能提升约 3 倍

5.6 CMake 项目示例

# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(lockfree_demo CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Threads REQUIRED)

add_executable(spinlock      spinlock.cpp)
add_executable(spsc_queue    spsc_queue.cpp)
add_executable(lockfree_stack lockfree_stack.cpp)
add_executable(dclp          dclp.cpp)
add_executable(false_sharing false_sharing.cpp)

foreach(target spinlock spsc_queue lockfree_stack dclp false_sharing)
    target_link_libraries(${target} PRIVATE Threads::Threads)
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
        target_compile_options(${target} PRIVATE -O2 -Wall -Wextra -fsanitize=address,undefined)
        target_link_options(${target} PRIVATE -fsanitize=address,undefined)
    endif()
endforeach()

# ThreadSanitizer 目标(专门用于检测数据竞争)
add_executable(spinlock_tsan spinlock.cpp)
target_compile_options(spinlock_tsan PRIVATE -O1 -fsanitize=thread)
target_link_options(spinlock_tsan PRIVATE -fsanitize=thread)

5.7 fetch_add 实现 Armstrong 数计算

// file: armstrong.cpp
// 演示 fetch_add 的常见用法
#include <atomic>
#include <vector>
#include <thread>
#include <iostream>
#include <cmath>

bool isArmstrong(int n) {
    int original = n, sum = 0, digits = 0;
    int temp = n;
    while (temp) { digits++; temp /= 10; }
    temp = n;
    while (temp) {
        int d = temp % 10;
        sum += std::pow(d, digits);
        temp /= 10;
    }
    return sum == original;
}

int main() {
    constexpr int N = 1'000'000;
    std::atomic<int> count{0};

    auto worker = [&](int start, int end) {
        int local_count = 0;
        for (int i = start; i < end; ++i) {
            if (isArmstrong(i)) ++local_count;
        }
        count.fetch_add(local_count, std::memory_order_relaxed);
    };

    int num_threads = std::thread::hardware_concurrency();
    std::vector<std::thread> threads;
    int chunk = N / num_threads;
    for (int i = 0; i < num_threads; ++i) {
        int start = i * chunk;
        int end = (i == num_threads - 1) ? N : start + chunk;
        threads.emplace_back(worker, start, end);
    }
    for (auto& t : threads) t.join();

    std::cout << "Armstrong numbers < " << N << ": " << count << "\n";
    // 输出: Armstrong numbers < 1000000: 16
    return 0;
}

6. 对比分析

6.1 与 Rust 内存模型对比

维度C++Rust
内存模型C++11借鉴 C++11 与 JMM
原子 APIstd::atomic<T>std::sync::atomic::*
内存序数量6 种5 种(无 consume)
Unsafe 检查unsafe 块要求
数据竞争 UBUB(C++20 起可被 sanitizer 检测)编译期禁止(borrow checker)
无锁库自实现 / boost::lockfreecrossbeamtokio
cache line paddingalignas(64)#[repr(align(64))]

6.2 与 Java JMM 对比

维度C++Java
内存模型C++11JMM (JSR-133)
volatile 语义无(C++ volatile 与并发无关)类似 seq_cst 原子
原子 APIstd::atomic<T>AtomicInteger
Happens-before形式化定义形式化定义
final 字段保证final 字段初始化后对所有线程可见
异常安全移动语义 + noexcepttry-catch + finally

6.3 与 Go 内存模型对比

维度C++Go
内存模型C++11 (2011)Go MM (2009, 修订 2022)
原子操作std::atomic<T>sync/atomic
内存序选项6 种3 种(SeqCst/Acquire/Release)
协程支持std::threadstd::coroutine(C++20)goroutine 内置
Channel 通信无内置chan 内置(CSP 模型)
Race DetectorTSango test -race

6.4 6 种内存序性能对比

内存序x86 编译开销ARM 编译开销适用场景
relaxed无(普通 mov)无(普通 ldr/str)计数器、统计
consume通常退化为 acquire类似 acquire数据依赖场景(少用)
acquire(load)无(普通 mov)dmb ishld与 release 配对
release(store)无(普通 mov)dmb ish + str与 acquire 配对
acq_rel(RMW)普通 lock xaddldaxr/stlxr + dmbRMW 操作
seq_cst(load)mfencelockdmb ish全局总序需求
seq_cst(store)xchgmov + mfencedmb ish + str + dmb ish全局总序需求

经验法则:默认 seq_cst,性能敏感时降为 acquire/release,纯计数器用 relaxed

7. 常见陷阱与最佳实践

7.1 陷阱 1:误用 memory_order_relaxed

std::atomic<bool> ready{false};
int data = 0;

// 线程 A
data = 42;
ready.store(true, std::memory_order_relaxed);  // 错误!

// 线程 B
while (!ready.load(std::memory_order_relaxed));
print(data);  // 不保证看到 42

最佳实践:消息发布场景必须使用 release/acquire 配对。

7.2 陷阱 2:忘记 atomic 之外的内存访问

// 错误:data 不是 atomic
int data = 0;
bool flag = false;  // 非 atomic

// 线程 A
data = 42;
flag = true;  // 编译器/CPU 可能重排

// 线程 B
while (!flag);
print(data);  // UB:数据竞争

最佳实践:跨线程通信变量必须 std::atomic

7.3 陷阱 3:CAS 中的 ABA 问题

// 简化的无锁栈 pop
Node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next));
delete old_head;
// 问题:old_head 可能在 CAS 期间被 pop 后又 push 回来(A→B→A)
// 导致 old_head->next 已失效

解决方案

  1. 使用 std::atomic<std::shared_ptr<Node>>(C++20);
  2. 带版本号指针(DCAS 模拟);
  3. Hazard pointer(C++26);
  4. Epoch-based reclamation。

7.4 陷阱 4:compare_exchange_weak vs strong

// weak 版本可能"伪失败"(实际值等于 expected 但仍返回 false)
while (!head.compare_exchange_weak(expected, desired));

// 强版本不会伪失败,但循环场景仍推荐 weak(性能更优)
if (head.compare_exchange_strong(expected, desired)) { ... }

最佳实践:循环场景用 weak,单次判断用 strong

7.5 陷阱 5:忘记 false sharing

struct Bad {
    std::atomic<int> a, b, c, d;  // 大概率在同一缓存行
};

最佳实践:高频访问的原子变量使用 alignas(64)

7.6 陷阱 6:误用 volatile

volatile bool ready = false;  // C++ volatile 不保证原子性!
// 线程 A: ready = true;
// 线程 B: while (!ready);
// 这是 UB:数据竞争

最佳实践:跨线程通信使用 std::atomic,不要用 volatile

7.7 陷阱 7:内存序不匹配

// 线程 A: data = 42; flag.store(true, release);
// 线程 B: while (!flag.load(relaxed)); print(data);  // 错误:acquire 缺失

最佳实践:release 必须配对 acquire,否则不建立 synchronizes-with 关系。

7.8 陷阱 8:fence 与 atomic 操作混淆

// 错误:fence 不能单独起作用,必须配对原子操作
std::atomic_thread_fence(std::memory_order_release);
// 还需 store 一个原子变量才能建立同步

最佳实践:fence 与一个原子操作配对使用,建立 release-acquire 关系。

7.9 UB 清单

UB 类型描述检测方法
数据竞争非 atomic 变量跨线程读写TSan
ABA 问题CAS 通过但中间状态已变Hazard pointer / shared_ptr
错误的内存序缺少 acquire/release代码审查 + TSan
False sharing性能退化(非 UB)perf c2c
移动后语义假设假设 atomic 移动后值UBSan
volatile 误用跨线程非 atomicTSan

7.10 最佳实践清单

  1. 默认 seq_cst:除非性能测试证明需要降低。
  2. 优先 atomic:跨线程通信的变量必须是 std::atomic
  3. 配对使用:release 必须与 acquire 配对。
  4. 避免 false sharingalignas(64) 隔离高频原子变量。
  5. 慎用 fence:除非有明确理由,优先用 atomic 内置内存序。
  6. 测试时启用 TSan-fsanitize=thread 检测数据竞争。
  7. 使用现成无锁库:如 boost::lockfreemoodycamel::ConcurrentQueue
  8. 慎用 consume:C++17 起 deprecated,C++23 重新规范但仍少用。

8. 工程实践

8.1 构建与依赖

cmake_minimum_required(VERSION 3.20)
project(my_concurrent_lib CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Threads REQUIRED)
find_package(Boost REQUIRED COMPONENTS lockfree)

add_library(my_concurrent STATIC
    src/queue.cpp
    src/stack.cpp
)
target_link_libraries(my_concurrent PUBLIC Threads::Threads Boost::lockfree)

# 启用 ThreadSanitizer(仅 Debug)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
if(ENABLE_TSAN)
    add_compile_options(-fsanitize=thread -g)
    add_link_options(-fsanitize=thread)
endif()

8.2 性能基准测试

// file: bench_atomic.cpp
// compile: g++ -std=c++20 -O2 -pthread -o bench bench_atomic.cpp -lbenchmark
#include <benchmark/benchmark.h>
#include <atomic>
#include <thread>
#include <vector>

static void BM_AtomicRelaxed(benchmark::State& state) {
    std::atomic<int> counter{0};
    for (auto _ : state) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}
BENCHMARK(BM_AtomicRelaxed)->Threads(1)->Threads(4)->Threads(8);

static void BM_AtomicSeqCst(benchmark::State& state) {
    std::atomic<int> counter{0};
    for (auto _ : state) {
        counter.fetch_add(1, std::memory_order_seq_cst);
    }
}
BENCHMARK(BM_AtomicSeqCst)->Threads(1)->Threads(4)->Threads(8);

static void BM_FalseSharing(benchmark::State& state) {
    struct Bad { std::atomic<int> a, b; };
    Bad bad;
    for (auto _ : state) {
        bad.a.fetch_add(1, std::memory_order_relaxed);
        bad.b.fetch_add(1, std::memory_order_relaxed);
    }
}
BENCHMARK(BM_FalseSharing)->Threads(2);

static void BM_NoFalseSharing(benchmark::State& state) {
    struct Good {
        alignas(64) std::atomic<int> a;
        alignas(64) std::atomic<int> b;
    };
    Good good;
    for (auto _ : state) {
        good.a.fetch_add(1, std::memory_order_relaxed);
        good.b.fetch_add(1, std::memory_order_relaxed);
    }
}
BENCHMARK(BM_NoFalseSharing)->Threads(2);

BENCHMARK_MAIN();

8.3 调试技巧

1. ThreadSanitizer(TSan)检测数据竞争

g++ -std=c++20 -O1 -g -fsanitize=thread -fno-omit-frame-pointer \
    -o spinlock spinlock.cpp -pthread
./spinlock
# 若有数据竞争,TSan 会输出详细报告

2. AddressSanitizer + UBSan 检测内存错误

g++ -std=c++20 -O1 -g -fsanitize=address,undefined \
    -o spinlock spinlock.cpp -pthread

3. perf c2c 检测 false sharing

perf c2c record ./bench
perf c2c report

4. 使用 std::atomic::is_always_lock_free

static_assert(std::atomic<int>::is_always_lock_free);
// C++17 起保证某些原子操作是无锁的

8.4 编译器内置原子操作

GCC/Clang 提供 __atomic_* 内置函数:

int x = 0;
__atomic_store_n(&x, 42, __ATOMIC_RELEASE);
int v = __atomic_load_n(&x, __ATOMIC_ACQUIRE);
bool ok = __atomic_compare_exchange_n(&x, &expected, desired,
                                       false,  // weak
                                       __ATOMIC_ACQ_REL,
                                       __ATOMIC_ACQUIRE);

在 C 代码或非 C++ 标准库环境下可使用。

8.5 跨平台注意事项

// Windows 平台使用 Interlocked API
#include <windows.h>
LONG old = InterlockedExchangeAdd(&counter, 1);

// Linux 平台使用 __sync_* 或 stdatomic.h
#include <stdatomic.h>
atomic_fetch_add(&counter, 1);

// macOS 平台使用 OSAtomic
#include <libkern/OSAtomic.h>
int32_t old = OSAtomicAdd32(1, &counter);

8.6 CI/CD 集成

# .github/workflows/concurrent-ci.yml
name: Concurrent CI

on: [push, pull_request]

jobs:
  tsan:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - run: |
          sudo apt-get install -y clang-17 cmake ninja-build
          cmake -B build -G Ninja -DCMAKE_CXX_COMPILER=clang++-17 \
            -DENABLE_TSAN=ON
          cmake --build build
          cd build && ctest --output-on-failure

  asan:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - run: |
          sudo apt-get install -y g++-13 cmake ninja-build
          cmake -B build -G Ninja -DCMAKE_CXX_COMPILER=g++-13 \
            -DCMAKE_BUILD_TYPE=Debug
          cmake --build build
          cd build && ctest --output-on-failure

9. 案例研究

9.1 案例一:Linux 内核 READ_ONCE / WRITE_ONCE

Linux 内核使用 READ_ONCE(x)WRITE_ONCE(x, v) 替代普通访问,对应 C++ 的 atomic.load(relaxed)atomic.store(v, relaxed)

// Linux 内核
int x;
WRITE_ONCE(x, 42);   // 类似 atomic_store(&x, 42, relaxed)
int v = READ_ONCE(x); // 类似 atomic_load(&x, relaxed)

Linux 内存模型(LKMM)与 C++11 模型有细微差异,但基本概念一致。

9.2 案例二:Folly MicroLock

Facebook Folly 库的 MicroLock 通过位运算在单个 uint8_t 中存储 8 个锁状态,最大化缓存利用:

class MicroLockCore {
    uint8_t lock_byte_;
public:
    void lock(int idx) {
        uint8_t mask = 1 << idx;
        while (__builtin_popcount(lock_byte_ & mask) ||
               !__sync_bool_compare_and_swap(&lock_byte_, lock_byte_, lock_byte_ | mask)) {
            // 自旋
        }
    }
};

9.3 案例三:Intel TBB concurrent_vector

Intel TBB 的 concurrent_vector 通过分段增长(segmented growth)实现并发安全的增长操作:

  • 每段独立分配,避免全局锁;
  • 索引通过段号 + 段内偏移计算;
  • 增长时仅锁当前段。

9.4 案例四:Java ConcurrentHashMap

Java 的 ConcurrentHashMap 使用分段锁(Java 7)或 CAS + synchronized(Java 8+):

// Java 8+ 风格
Node<K,V> f; int n;
if ((f = tabAt(tab, i)) == null) {
    if (casTabAt(tab, i, null, new Node<>(...))) break;
} else {
    synchronized (f) { /* 链表插入 */ }
}

C++ 等价实现可参考 folly::AtomicHashMaptbb::concurrent_hash_map

9.5 案例五:DPDK 无锁环形缓冲

DPDK(Data Plane Development Kit)的 rte_ring 是高性能无锁环形队列,支持单生产者-单消费者(SPSC)和多生产者-多消费者(MPMC)模式:

// DPDK rte_ring 简化
struct rte_ring {
    struct rte_ring_head tail;
    struct rte_ring_head head;
    void* buffer[capacity];
};

int rte_ring_enqueue(struct rte_ring* r, void* obj) {
    uint32_t prod_head = r->head.prod;
    uint32_t prod_next = prod_head + 1;
    if (prod_next - r->tail.cons >= capacity) return -ENOBUFS;
    if (!__sync_bool_compare_and_swap(&r->head.prod, prod_head, prod_next))
        return -ENOBUFS;  // 重试
    r->buffer[prod_head & mask] = obj;
    __sync_synchronize();
    return 0;
}

10. 习题

10.1 选择题

Q1. 以下哪种内存序提供最强的同步保证?

  • A. relaxed
  • B. acquire
  • C. release
  • D. seq_cst
答案与解析

答案:D

seq_cst 提供全局总序,所有线程观察一致。是最强的内存序。

Q2. 以下代码是否安全?

int data = 0;
std::atomic<bool> ready{false};

// 线程 A
data = 42;
ready.store(true, std::memory_order_relaxed);

// 线程 B
while (!ready.load(std::memory_order_acquire));
print(data);
  • A. 安全
  • B. 不安全,存在数据竞争
  • C. 安全但性能差
  • D. 编译错误
答案与解析

答案:B

A 线程使用了 relaxed store,不建立 synchronizes-with 关系。即使 B 线程使用 acquire load,A 线程的 data = 42 也不保证对 B 可见。需要 A 使用 release store。

Q3. ABA 问题主要发生在什么场景?

  • A. 多生产者多消费者的无锁栈
  • B. 单生产者单消费者队列
  • C. 互斥锁实现
  • D. 全部以上
答案与解析

答案:A

ABA 问题主要发生在使用 CAS 的无锁数据结构中,特别是无锁栈/队列。SPSC 队列通常不涉及 ABA(无并发 pop)。

Q4. compare_exchange_weakstrong 的区别是什么?

  • A. weak 性能更好
  • B. strong 保证不会伪失败
  • C. weak 在循环中使用更优
  • D. 全部以上
答案与解析

答案:D

weak 可能伪失败(即使值匹配也返回 false),但在循环场景中性能更优(避免不必要的内存屏障)。strong 保证不会伪失败,适合单次判断。

10.2 填空题

Q1. C++11 定义了 ______ 种 std::memory_order

答案

6 种:relaxed, consume, acquire, release, acq_rel, seq_cst。

Q2. std::atomic<T> 默认内存序是 ______。

答案

seq_cst

Q3. x86 上 mfence 指令对应 C++ 的 ______ 内存序。

答案

seq_cst

Q4. 缓存行通常大小为 ______ 字节,可通过 ______ 关键字对齐。

答案

64;alignas(64)

10.3 编程题

Q1. 实现一个无锁自旋锁 SpinLock,使用 acquire/release 内存序。

参考答案
#include <atomic>

class SpinLock {
    std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
public:
    void lock() {
        while (flag_.test_and_set(std::memory_order_acquire)) {
            // 自旋
        }
    }
    void unlock() {
        flag_.clear(std::memory_order_release);
    }
};

Q2. 实现一个简单的 MPMCCounter,允许多线程递增,最终读取总数。

参考答案
#include <atomic>

class MPMCCounter {
    std::atomic<long> count_{0};
public:
    void increment() {
        count_.fetch_add(1, std::memory_order_relaxed);
    }
    long get() const {
        return count_.load(std::memory_order_acquire);
    }
};

Q3. 实现一个简化版的 LockFreeQueue<T>(单生产者单消费者)。

参考答案
#include <atomic>
#include <cstddef>

template <typename T, size_t Capacity>
class SPSCQueue {
    alignas(64) std::atomic<size_t> write_pos_{0};
    alignas(64) std::atomic<size_t> read_pos_{0};
    T buffer_[Capacity];
public:
    bool push(T value) {
        size_t wp = write_pos_.load(std::memory_order_relaxed);
        size_t rp = read_pos_.load(std::memory_order_acquire);
        if (wp - rp >= Capacity) return false;
        buffer_[wp % Capacity] = std::move(value);
        write_pos_.store(wp + 1, std::memory_order_release);
        return true;
    }
    bool pop(T& out) {
        size_t rp = read_pos_.load(std::memory_order_relaxed);
        size_t wp = write_pos_.load(std::memory_order_acquire);
        if (rp == wp) return false;
        out = std::move(buffer_[rp % Capacity]);
        read_pos_.store(rp + 1, std::memory_order_release);
        return true;
    }
};

10.4 思考题

Q1. 为什么 seq_cst 在 x86 上比 relaxed 贵得多?

参考解析

x86 的 TSO 模型允许 store-load 重排。seq_cst 需要禁止这种重排,必须插入 mfence 指令或使用 xchg(隐含 lock 前缀)。这些操作会强制刷新写缓冲,导致显著的性能开销(5-10 倍)。而 relaxed 仅是普通的 mov 指令,几乎免费。

Q2. 无锁数据结构一定比互斥锁快吗?

参考解析

不一定。无锁数据结构在高争用场景下可能反而更慢:

  • CAS 失败重试导致 CPU 浪费;
  • 缓存行迁移开销大;
  • 实现复杂度高,易出错。

低争用场景下,互斥锁(如 std::mutex)通常足够快(内核态 futex),且实现简单、易维护。无锁结构适合特定场景:高频访问、低延迟需求、无法进入内核态的实时系统。

工程实践中应先测量再优化,避免过早使用无锁。

Q3. 为什么 C++ 不像 Java 那样使用 volatile 实现跨线程可见性?

参考解析

C++ 的 volatile 语义与 Java 不同:

  • C++ volatile 仅禁止编译器优化(如寄存器缓存),不保证 CPU 层面的可见性;
  • Java volatile 类似 C++ 的 std::atomic + seq_cst

C++ 选择将 volatile 与并发分离,引入 std::atomic 专门处理跨线程同步。这种分离使语义更清晰,避免误用 volatile 导致的微妙并发 bug。

C++20 起 volatile 进一步被弱化(deprecate 了许多用途)。

11. 参考文献

引用采用 ACM Reference Format,含 DOI 链接。

  1. Boehm, H.-J. and Adve, A. 2008. Foundations of the C++ Concurrency Memory Model. In Proceedings of the 29th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI ‘08). ACM, New York, NY, USA, 68–78. DOI: 10.1145/1375581.1375591

  2. Boehm, H.-J. 2005. Memory Model for C++. ISO/IEC JTC1/SC22/WG21 N2007. Available at: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n2007.pdf

  3. ISO/IEC. 2023. Information technology — Programming languages — C++. ISO/IEC 14882:2023. International Organization for Standardization, Geneva, Switzerland. Available at: https://www.iso.org/standard/83626.html

  4. Williams, A. 2019. C++ Concurrency in Action (2nd ed.). Manning Publications, Shelter Island, NY, USA. ISBN: 978-1617294693.

  5. Herlihy, M. and Shavit, N. 2012. The Art of Multiprocessor Programming (2nd ed.). Morgan Kaufmann, Cambridge, MA, USA. ISBN: 978-0123973375.

  6. McKenney, P. E. and Silvera, A. 2009. Memory Barriers: a Hardware View for Software Hackers. Available at: https://www.puppet.com/sites/default/files/2017-08/memory-barriers.pdf

  7. Adve, S. V. and Gharachorloo, K. 1996. Shared Memory Consistency Models: A Tutorial. IEEE Computer 29, 12, 66–76. DOI: 10.1109/2.546611

  8. Crowl, L. and Boehm, H.-J. 2007. Atomic Operations in C++. ISO/IEC JTC1/SC22/WG21 N2145. Available at: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2145.html

  9. Lea, D. 2019. The java.util.concurrent Synchronizer Framework. In Proceedings of the 19th European Conference on Object-Oriented Programming (ECOOP ‘05). Springer, Berlin, Germany, 1–20. DOI: 10.1007/11531142_1

  10. Michael, M. M. 2004. Hazard Pointers: Safe Memory Reclamation for Lock-Free Objects. IEEE Transactions on Parallel and Distributed Systems 15, 6, 491–504. DOI: 10.1109/TPDS.2004.8

  11. Detlefs, D., Flood, C., Garthwaite, A., Martin, P., Shavit, N., and Steele, G. 2002. Even Better DCAS-Based Concurrent Deques. In Proceedings of the 14th International Conference on Distributed Computing (DISC ‘00). Springer, Berlin, Germany, 72–86. DOI: 10.1007/3-540-40026-5_5

  12. Michael, M. M. and Scott, M. L. 1996. Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms. In Proceedings of the 15th Annual ACM Symposium on Principles of Distributed Computing (PODC ‘96). ACM, New York, NY, USA, 267–275. DOI: 10.1145/248052.248106

  13. Mckenney, P. E. 2020. Hazard Pointers. ISO/IEC JTC1/SC22/WG21 P2530R0. Available at: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2530r0.html

  14. Sutter, H. 2012. Welcome to the Jungle. ISO/IEC JTC1/SC22/WG21 N3334. Available at: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3334.html

  15. Lamport, L. 1979. How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs. IEEE Transactions on Computers 28, 9, 690–691. DOI: 10.1109/TC.1979.1675439

12. 延伸阅读

12.1 书籍

  • 《C++ Concurrency in Action》(Anthony Williams, 2nd ed., 2019):C++ 并发权威著作,详细讲解内存模型与无锁编程。
  • 《The Art of Multiprocessor Programming》(Maurice Herlihy, Nir Shavit, 2nd ed., 2012):无锁算法理论经典。
  • 《Concurrency in Practice》(Brian Goetz et al., 2006):Java 视角,但内存模型概念通用。
  • 《Java Concurrency in Practice》(Brian Goetz, 2006):JMM 详解。
  • 《Programming with POSIX Threads》(David R. Butenhof, 1997):低层线程 API。

12.2 论文与提案

  • N2007: Memory Model for C++ (Boehm, 2005) — C++ 内存模型奠基。
  • N2348: A Strong and Safe Memory Model for C++ (Boehm, 2007) — 最终版本。
  • P0019: atomic_ref (Halpern, 2017) — C++17 引入 atomic_ref。
  • P1135: The C++20 Synchronization Library — semaphore/latch/barrier。
  • P2530: Hazard Pointers for C++26 — hazard pointer 标准化。
  • P2545: Read-Copy-Update (RCU) for C++26 — RCU 标准化。
  • P2045: atomic_ref — 浮点原子操作。

12.3 在线资源

12.4 视频课程

  • CPPCon: C++ Memory Model (Herb Sutter, 2019) — 内存模型概览。
  • CPPCon: Lock-Free Programming (Fedor Pikus, 2018) — 无锁编程实战。
  • MIT 6.172: Performance Engineering of Software Systems — 多核性能优化。
  • Stanford CS149: Parallel Computing — 并行计算原理。
  • CMU 15-440: Distributed Systems — 分布式系统(含内存一致性模型)。

12.5 开源项目源码阅读


附录 A:6 种内存序速查表

内存序load 语义store 语义RMW 语义典型用途
relaxed计数器、统计
consume数据依赖依赖链同步(少用)
acquire后续读/写不重排到前与 release 配对 load
release之前的读/写不重排到后与 acquire 配对 store
acq_relacquirereleaseacquire + releaseRMW(如 exchange)
seq_cstacquire + 全局序release + 全局序acq_rel + 全局序默认值,全局总序

附录 B:架构内存模型速查

架构模型允许重排屏障指令
x86 / x86-64TSOstore-loadmfence, lock 前缀
ARMv7Relaxed全部dmb, dsb, isb
ARMv8 / AArch64Relaxed (with acquire/release)全部ldar, stlr
POWERRelaxed全部lwsync, sync, isync
AlphaRelaxed + speculative read全部 + speculativemb
RISC-VRVWMO全部fence
ItaniumRelaxed全部mf, mf.a

附录 C:常用原子操作 API

#include <atomic>

// 基本读写
T load(memory_order = seq_cst);
void store(T, memory_order = seq_cst);

// RMW(read-modify-write)
T exchange(T, memory_order = seq_cst);
bool compare_exchange_weak(T& expected, T desired,
                           memory_order success, memory_order failure);
bool compare_exchange_strong(T& expected, T desired,
                             memory_order success, memory_order failure);

// 算术运算(仅整型与指针)
T fetch_add(T, memory_order = seq_cst);
T fetch_sub(T, memory_order = seq_cst);
T fetch_and(T, memory_order = seq_cst);
T fetch_or(T, memory_order = seq_cst);
T fetch_xor(T, memory_order = seq_cst);

// C++20 新增
bool wait(T old, memory_order = seq_cst);
void notify_one();
void notify_all();

// 类型特征
static constexpr bool is_always_lock_free;
bool is_lock_free() const noexcept;

// Fence
void atomic_thread_fence(memory_order);
void atomic_signal_fence(memory_order);

附录 D:常见错误代码模式

// 错误 1:non-atomic 跨线程读写
int data = 0;
// 线程 A: data = 42;
// 线程 B: print(data);  // UB

// 错误 2:内存序不匹配
// 线程 A: data = 42; flag.store(true, release);
// 线程 B: while (!flag.load(relaxed));  // 应使用 acquire

// 错误 3:ABA 问题
// Node* old = head.load();
// while (!head.compare_exchange_weak(old, old->next));
// delete old;  // 可能 dangling

// 错误 4:false sharing
// struct Bad { atomic<int> a, b; };

// 错误 5:volatile 误用
// volatile bool ready = false;  // 不是 atomic

// 错误 6:fence 单独使用
// atomic_thread_fence(release);  // 必须配对原子操作

// 错误 7:consume 用作 acquire
// x.load(consume)  // 仅同步数据依赖,不能保证其他变量

// 错误 8:CAS 后忘记更新 expected
// while (!head.compare_exchange_weak(old, new)) { /* old 应自动更新 */ }

更新日志

  • 2026-07-20: 金标准升级至对标 MIT/Stanford/CMU 教学水准,新增历史脉络、形式化定义、KaTeX 推导、企业级示例、案例研究、习题与参考文献。
  • 2026-06-14: 初版,覆盖内存序基本概念与简单无锁栈。
返回入门指南