前置知识: C++

内存序与无锁编程

17 min高级

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

前置知识

学习目标

  • 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
  • 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
  • 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
  • 掌握「4. 代码示例(企业级 production-ready)」的核心机制、典型用法与常见陷阱
  • 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱

1. 历史动机与发展脉络

1.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

1.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 规则,更接近硬件内存模型。

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

标准关键变化文档编号
C++11引入 std::atomic、6 种 std::memory_order、std::atomic_thread_fence、std::atomic_signal_fence、std::mutex 系列N2348, N2752
C++14std::shared_timed_mutex 用于读写锁;constexpr 原子操作N3659
C++17std::scoped_lock 多锁获取;memory_order_consume 规范进入修订、临时不建议使用P0156
C++20std::atomic_ref<T>(对已有对象的原子访问,P0019);std::counting_semaphore、std::latch、std::barrier;std::atomic 扩展到 std::shared_ptr;atomic::wait/notifyP0019, P0718, P1135
C++23std::atomic 增补 fetch_min/fetch_max(含浮点类型)P0493
C++26(草案)Hazard pointer(P2530)、std::rcu(P2545);std::execution sender/receiver 框架(P2300);memory_order_consume 正式废弃P2530, P2545, P2300

1.4 与其他语言的横向对比

特性C++JavaRustGoC#
内存模型形式化C++11JMM (JSR-133)借鉴 C++ 与 JMMGo MMECMA-335
原子操作 APIstd::atomic<T>VarHandle/AtomicXxxstd::sync::atomicsync/atomicInterlocked/Volatile
内存序选项6 种volatile、final、happens-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]

2. 形式化定义

2.1 C++ 内存模型基础

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

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

2.2 Happens-before 关系

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

A→hbB  ⟺  {A sequenced-before B(同一线程内的顺序关系)A synchronizes-with B(跨线程同步关系)A→hbC∧C→hbB(传递闭包)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 建立。

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

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

枚举值语义同步保证
memory_order_relaxed仅保证原子性,不提供顺序保证无
memory_order_consume名义上仅约束「数据依赖」该 load 的操作;自 C++17 起规范进入修订、不建议使用,主流编译器一律按 acquire 实现,C++26 起正式废弃事实等价于 acquire
memory_order_acquire当前线程内后续的读/写不能重排到 load 之前与 release 配对
memory_order_release当前线程内之前的读/写不能重排到 store 之后与 acquire 配对
memory_order_acq_relload 与 store 都有相应保证acquire + release
memory_order_seq_cst全局总序,所有线程观察到的顺序一致最强

形式化表达:

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

2.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,所有线程观察一致。

2.5 内存屏障的形式化

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

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

2.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。

3. 理论推导与原理解析

3.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

3.2 CPU 内存模型对比

3.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]

3.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)

3.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 + mfence 或 lock xaddldr + dmb ishx86: 5-10x; ARM: 2-3x
seq_cst(store)xchg 或 mov + mfencedmb ish + str + dmb ishx86: 5-10x; ARM: 3-5x

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

3.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 ∗ptr←desiredfalseotherwise, expected←∗ptr\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   // 失败时
);

3.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 机制标准化。

3.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

3.6 公平性与无锁性等级

按 Herlihy-Shavit 分类:

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

形式化:

progress(system)={wait-free∀t∈threads,t always progresseslock-free∃t∈threads,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}

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

4.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;
}

4.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;
}

4.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;
}

4.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;
}

4.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 倍。

4.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)

4.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;
}

5. 对比分析

5.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::lockfreecrossbeam、tokio
cache line paddingalignas(64)#[repr(align(64))]

5.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

5.3 与 Go 内存模型对比

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

5.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)mfence 或 lockdmb ish全局总序需求
seq_cst(store)xchg 或 mov + mfencedmb ish + str + dmb ish全局总序需求

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

6. 常见陷阱与最佳实践

6.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 配对。

6.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。

6.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。

6.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。

6.5 陷阱 5:忘记 false sharing

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

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

6.6 陷阱 6:误用 volatile

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

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

6.7 陷阱 7:内存序不匹配

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

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

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

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

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

6.9 UB 清单

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

6.10 最佳实践清单

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

7. 工程实践

7.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()

7.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();

7.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 起保证某些原子操作是无锁的

7.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++ 标准库环境下可使用。

7.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);

7.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

8. 案例研究

8.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 模型有细微差异,但基本概念一致。

8.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)) {
            // 自旋
        }
    }
};

8.3 案例三:Intel TBB concurrent_vector

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

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

8.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::AtomicHashMap 或 tbb::concurrent_hash_map。

8.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;
}

填空题知识点讲解

常见疑问 5:. C++11 定义了 ______ 种 std::memory_order。

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

常见疑问 6:. std::atomic<T> 默认内存序是 ______。

seq_cst。

常见疑问 7:. x86 上 mfence 指令对应 C++ 的 ______ 内存序。

seq_cst。

常见疑问 8:. 缓存行通常大小为 ______ 字节,可通过 ______ 关键字对齐。

64;alignas(64)。

编程题知识点讲解

常见疑问 9:. 实现一个无锁自旋锁 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);
    }
};

常见疑问 10:. 实现一个简单的 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);
    }
};

常见疑问 11:. 实现一个简化版的 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;
    }
};

11.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。

11.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) — 提案于 2017 年提交,随 C++20 正式纳入标准。
  • 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 — 浮点原子操作。

11.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 — 分布式系统(含内存一致性模型)。

11.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 应自动更新 */ }

原子类型

基本写法:声明原子变量 std::atomic<<类型>> <变量>;

// 原子整型变量
std::atomic<int> counter{0};

基本写法:原子加载 <变量>.load([<内存序>]);

// 原子读取值
int v = counter.load(std::memory_order_acquire);

基本写法:原子存储 <变量>.store(<值>, [<内存序>]);

// 原子写入值
counter.store(10, std::memory_order_release);

基本写法:原子交换 <变量>.exchange(<值>, [<内存序>]);

// 原子替换并返回旧值
int old = counter.exchange(5);

CAS 操作

基本写法:比较并交换 <变量>.compare_exchange_strong(<期望>, <新值>, [<内存序>]);

// 强版本 CAS,失败时更新期望值
int expected = 0;
bool ok = counter.compare_exchange_strong(expected, 1);

基本写法:弱版本 CAS <变量>.compare_exchange_weak(<期望>, <新值>);

// 可能伪失败,适合循环中
while (!counter.compare_exchange_weak(expected, expected + 1));

基本写法:fetch_add 原子加法 <变量>.fetch_add(<值>, [<内存序>]);

// 原子加并返回旧值
int prev = counter.fetch_add(1);

基本写法:fetch_sub 原子减法 <变量>.fetch_sub(<值>, [<内存序>]);

// 原子减并返回旧值
int prev = counter.fetch_sub(1);

内存序

基本写法:顺序一致性 std::memory_order_seq_cst

// 最强保证,全局总序
counter.store(1, std::memory_order_seq_cst);

基本写法:获取语义 std::memory_order_acquire

// 加载时保证后续读不重排到此之前
v = counter.load(std::memory_order_acquire);

基本写法:释放语义 std::memory_order_release

// 存储时保证之前写不重排到此之后
counter.store(1, std::memory_order_release);

基本写法:宽松语义 std::memory_order_relaxed

// 仅保证原子性无顺序约束
counter.fetch_add(1, std::memory_order_relaxed);

fence 屏障

基本写法:释放屏障 std::atomic_thread_fence(std::memory_order_release);

// 显式内存屏障防止写重排
std::atomic_thread_fence(std::memory_order_release);
data = 42;
ready.store(true);

基本写法:获取屏障 std::atomic_thread_fence(std::memory_order_acquire);

// 显式内存屏障防止读重排
std::atomic_thread_fence(std::memory_order_acquire);
int v = data;

自旋锁示例

基本写法:使用原子实现自旋锁 while (<锁>.test_and_set(std::memory_order_acquire)) {}

// 原子标志位自旋等待
std::atomic_flag lock = ATOMIC_FLAG_INIT;
while (lock.test_and_set(std::memory_order_acquire)) {}
// 临界区
lock.clear(std::memory_order_release);

基本写法:等待与通知 <变量>.wait(<旧值>); <变量>.notify_one();

// C++20 原子等待通知
counter.wait(0);          // 阻塞直到值变化
counter.store(1);
counter.notify_one();     // 唤醒一个等待者