C++ 第三阶段 并发与异步 - 第三节:原子操作(std::atomic)

目录

一、std::atomic 概述

1. 原子操作的核心概念

2. std::atomic 的作用

二、std::atomic 的基本用法

1. 声明与初始化

2. 基本操作

示例:计数器

三、内存顺序(Memory Order)

1. 内存顺序的作用

示例:内存顺序的使用

四、常见问题与解决方案

1. 数据竞争的误判

2. ABA 问题

3. 过度使用原子操作

五、最佳实践

1. 选择合适的内存顺序

2. 避免原子操作嵌套

3. 使用无锁数据结构时验证硬件支持

4. 合理设计原子变量

六、示例代码汇总

示例 1:原子标志位

示例 2:无锁队列(简化版)

示例 3:ABA 问题的解决方案

七、总结

1. std::atomic 的核心优势

2. 适用场景

3. 后续学习方向

C++从入门到入土学习导航_c++学习进程-CSDN博客


一、std::atomic 概述

1. 原子操作的核心概念

原子操作(Atomic Operation)是一种不可中断的操作,在多线程环境中,其执行过程要么完全完成,要么未开始,不会被其他线程干扰。它通过硬件指令(如 CAS)实现,确保共享数据的线程安全。

  • 硬件支持:现代处理器提供原子指令(如 x86 的 CMPXCHG、ARM 的 LDREX/STREX),std::atomic 封装了这些底层操作。
  • 关键特性:避免数据竞争(Data Race),保证操作的可见性和有序性。

2. std::atomic 的作用

  • 解决数据竞争:保证对共享变量的读写操作不会被其他线程打断。
  • 替代轻量级锁:在简单场景中,原子操作比互斥锁(std::mutex)性能更高。
  • 支持无锁编程:通过组合原子操作实现无锁数据结构(如队列、栈)。

二、std::atomic 的基本用法

1. 声明与初始化

#include <atomic>

std::atomic<int> counter(0);       // 声明并初始化为 0
std::atomic<bool> flag(false);     // 声明布尔型原子变量
std::atomic<std::string*> ptr(nullptr); // 指针类型的原子变量

2. 基本操作

方法描述
load()原子读取当前值。
store(value)原子写入新值。
exchange(value)原子交换值并返回旧值。
compare_exchange_weak(expected, desired)比较并交换(CAS),若当前值等于 expected,则更新为 desired
fetch_add(delta)原子增加并返回旧值。
fetch_sub(delta)原子减少并返回旧值。
示例:计数器
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed); // 原子增加
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

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

    std::cout << "Counter value: " << counter << std::endl;
    return 0;
}

三、内存顺序(Memory Order)

1. 内存顺序的作用

内存顺序(std::memory_order)控制原子操作的内存可见性和指令重排规则,影响性能和正确性。常见的选项包括:

内存顺序含义适用场景
memory_order_relaxed仅保证原子性,不强制内存顺序。非关键数据同步(如计数器)。
memory_order_acquire保证后续操作不会重排到当前操作之前。读操作后的同步。
memory_order_release保证前面的操作不会重排到当前操作之后。写操作前的同步。
memory_order_acq_rel结合 acquire 和 release。读-修改-写操作。
memory_order_seq_cst严格顺序一致性(默认)。通用同步需求。
示例:内存顺序的使用
std::atomic<int> x(0);
std::atomic<int> y(0);

void thread1() {
    x.store(1, std::memory_order_relaxed); // 无同步
    y.store(1, std::memory_order_release); // 写后同步
}

void thread2() {
    int y_val = y.load(std::memory_order_acquire); // 读前同步
    if (y_val == 1) {
        int x_val = x.load(std::memory_order_relaxed);
        std::cout << "x = " << x_val << std::endl;
    }
}

四、常见问题与解决方案

1. 数据竞争的误判

问题:

即使使用了原子操作,复合操作(如 x++)仍可能导致不一致状态。

std::atomic<int> x(0), y(0);
void threadA() { x.store(1, std::memory_order_relaxed); }
void threadB() { y.store(1, std::memory_order_relaxed); }
void threadC() {
    while (x.load(std::memory_order_relaxed) == 0 || y.load(std::memory_order_relaxed) == 0);
    if (x.load(std::memory_order_relaxed) != y.load(std::memory_order_relaxed)) {
        std::cout << "Inconsistent state!" << std::endl;
    }
}
解决方案:
  • 使用互斥锁保护复合操作
    std::mutex mtx;
    void threadC() {
        std::lock_guard<std::mutex> lock(mtx);
        if (x.load() != y.load()) {
            std::cout << "Inconsistent state!" << std::endl;
        }
    }

2. ABA 问题

问题:

当原子变量值从 A 变为 B 再变回 A,CAS 操作可能误判为未改变。

std::atomic<int> value(10);
void thread() {
    int expected = value.load();
    while (!value.compare_exchange_weak(expected, 20));
}
解决方案:
  • 增加版本号(Tag)
    struct tagged_value {
        int value;
        int tag;
    };
    std::atomic<tagged_value> atomic_value{{10, 0}};

3. 过度使用原子操作

问题:

频繁的原子操作可能导致性能下降(如缓存行争用)。

解决方案:
  • 减少原子操作频率:批量处理数据或合并操作。
  • 使用无锁队列优化:例如环形缓冲区(Ring Buffer)。

五、最佳实践

1. 选择合适的内存顺序

  • 优先使用 memory_order_seq_cst:除非性能要求极高。
  • 避免滥用 memory_order_relaxed:仅在非关键路径中使用。

2. 避免原子操作嵌套

  • 禁止在原子操作中调用非原子函数:可能导致未定义行为。

3. 使用无锁数据结构时验证硬件支持

static_assert(std::atomic<int>::is_always_lock_free, "int is not lock-free");

4. 合理设计原子变量

  • 将相关变量组合为结构体:避免多个原子变量的同步问题。
    struct shared_data {
        std::atomic<int> counter;
        std::atomic<bool> flag;
    };

六、示例代码汇总

示例 1:原子标志位

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<bool> ready(false);

void worker() {
    while (!ready.load(std::memory_order_acquire)) {
        std::this_thread::yield();
    }
    std::cout << "Worker thread is running!" << std::endl;
}

int main() {
    std::thread t(worker);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ready.store(true, std::memory_order_release);
    t.join();
    return 0;
}

示例 2:无锁队列(简化版)

#include <atomic>
#include <thread>
#include <vector>
#include <memory>

template <typename T>
class LockFreeQueue {
    struct Node {
        T data;
        std::atomic<std::shared_ptr<Node>> next;
    };
    std::atomic<std::shared_ptr<Node>> head;
    std::atomic<std::shared_ptr<Node>> tail;

public:
    LockFreeQueue() {
        auto node = std::make_shared<Node>(T{}, nullptr);
        head.store(node);
        tail.store(node);
    }

    void push(const T& value) {
        auto new_node = std::make_shared<Node>(value, nullptr);
        auto prev_tail = tail.load();
        prev_tail->next.store(new_node, std::memory_order_release); // 确保新节点数据可见
        tail.store(new_node, std::memory_order_release);
    }

    bool pop(T& result) {
        auto prev_head = head.load();
        auto next = prev_head->next.load(std::memory_order_acquire);
        if (!next) return false;
        result = next->data;
        head.store(next, std::memory_order_release);
        return true;
    }
};

示例 3:ABA 问题的解决方案

#include <atomic>
#include <thread>
#include <iostream>

struct tagged_value {
    int value;
    int tag;
};

std::atomic<tagged_value> atomic_val{{10, 0}};

void thread() {
    tagged_value expected = atomic_val.load();
    do {
        expected.value++;
        expected.tag++;
    } while (!atomic_val.compare_exchange_weak(expected, expected));
}

int main() {
    std::thread t1(thread);
    std::thread t2(thread);
    t1.join();
    t2.join();
    std::cout << "Final value: " << atomic_val.load().value << std::endl;
    return 0;
}

七、总结

1. std::atomic 的核心优势

  • 线程安全:通过硬件级原子指令保证操作的不可中断性。
  • 性能优化:相比互斥锁,适用于轻量级同步场景。
  • 灵活的内存控制:通过内存顺序调整同步行为,平衡性能与正确性。

2. 适用场景

  • 计数器与标志位:如在线用户数、任务完成标志。
  • 无锁数据结构:如队列、栈、哈希表。
  • 低开销同步:避免互斥锁的上下文切换开销。

3. 后续学习方向

  • 高级同步机制:如 std::atomic_ref(C++20)、原子引用。
  • 无锁算法设计:实现更复杂的并发数据结构。
  • 硬件特性优化:结合 CPU 缓存行对齐和原子指令特性提升性能。

通过掌握 std::atomic,开发者可以高效解决多线程环境下的数据竞争问题,构建高性能、线程安全的并发程序,为复杂系统设计提供坚实基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员弘羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值