<atomic> 是 C++ 标准库中提供的用于实现无锁编程和原子操作的头文件。通过 <atomic>,你可以对变量进行原子操作,避免在多线程环境下使用互斥量(mutex)而产生的额外开销和潜在死锁问题。该库支持内置类型以及用户自定义类型的原子操作,并且提供了内存序相关的接口,用于实现高效且线程安全的数据共享。


目录

  1. <atomic> 头文件简介
  2. 原子类型基础
  3. 内存序与操作
  4. 自定义类型的原子操作
  5. 常见注意事项
  6. 结论

1. <atomic> 头文件简介

<atomic> 提供了原子类型和相关操作,使得多个线程可以安全地共享数据而不需要显式加锁。原子操作通常是无锁的,因此能提供更高的性能和更低的延迟,同时减少死锁和竞争条件的风险。为了使用这些功能,你需要在程序中包含 <atomic> 头文件:

#include <atomic>


2. 原子类型基础

2.1 std::atomic

std::atomic 是模板类,用于包装各种类型以保证对其进行的操作都是原子的。常见用法包括:

  • 定义原子变量:std::atomic<int> counter;
  • 内置原子类型:如 std::atomic<bool>std::atomic_flagstd::atomic<int> 等。

示例:

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

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

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i)
        threads.push_back(std::thread(increment));

    for (auto& t : threads)
        t.join();

    std::cout << "Final counter value: " << counter.load() << std::endl;  // 输出:10000
    return 0;
}

在这个示例中,多个线程同时对 counter 进行增加操作,利用原子操作保证了线程安全。

2.2 原子操作示例

常用的原子操作包括:

  • load():读取变量的当前值。
  • store():将一个新值写入变量。
  • fetch_add()fetch_sub():原子地执行加法或减法操作,并返回旧值。
  • compare_exchange_weak()compare_exchange_strong():原子地比较和交换操作,常用于实现自旋锁和无锁数据结构。

示例:

#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> value(10);

    // 原子读取和写入
    int current = value.load(std::memory_order_acquire);
    value.store(20, std::memory_order_release);

    // 原子加法
    int old_value = value.fetch_add(5, std::memory_order_acq_rel);
    std::cout << "Old value: " << old_value << ", New value: " << value.load() << std::endl;

    // 原子比较交换
    int expected = 25;
    bool exchanged = value.compare_exchange_strong(expected, 30, std::memory_order_acq_rel);
    if (exchanged) {
        std::cout << "Exchange successful, value is now: " << value.load() << std::endl;
    } else {
        std::cout << "Exchange failed, expected was: " << expected << std::endl;
    }

    return 0;
}


3. 内存序与操作

原子操作允许指定内存序(memory order),用于控制操作在多线程环境下的可见性和排序。常见内存序选项包括:

  • std::memory_order_relaxed:无序的操作,不保证同步。
  • std::memory_order_acquire:用于读取操作,保证后续读写操作不会被重排序到其前面。
  • std::memory_order_release:用于写入操作,保证之前的操作不会被重排序到其后面。
  • std::memory_order_acq_rel:结合 acquire 和 release 的语义。
  • std::memory_order_seq_cst:顺序一致性,提供最严格的内存顺序保证(默认)。

选择合适的内存序有助于平衡性能和正确性。


4. 自定义类型的原子操作

除了内置类型,C++ 也允许对用户自定义的类型使用原子操作,但这些类型必须满足平凡拷贝和析构的要求,并且一般要求大小不超过机器字长。可以使用 std::atomic<T> 来包装自定义类型。

示例:

#include <iostream>
#include <atomic>

struct Data {
    int x;
    int y;
};

int main() {
    std::atomic<Data> atomicData;
    Data data = {1, 2};
    atomicData.store(data, std::memory_order_relaxed);
    
    Data loadedData = atomicData.load(std::memory_order_relaxed);
    std::cout << "Data: x = " << loadedData.x << ", y = " << loadedData.y << std::endl;
    
    return 0;
}

注意:对自定义类型的原子操作支持受到平台和编译器的限制,请查阅相关文档确保满足要求。


5. 常见注意事项

  • 无锁编程:原子操作允许无锁数据结构的实现,但要小心 ABA 问题和复杂的内存序管理。
  • 性能考虑:使用较弱的内存序(如 memory_order_relaxed)可以提高性能,但必须确保满足程序的同步需求。
  • 正确使用 compare_exchange:在使用比较交换操作时,要确保预期值更新正确,以避免错误的失败或无限循环。
  • 避免过度依赖原子:并非所有并发问题都适合无锁解决方案,有时合适的锁机制可以简化设计并提高代码可维护性。

6. 结论

C++ <atomic> 提供了一整套工具用于原子操作和无锁编程,能够帮助开发者在多线程环境下实现高效且安全的数据共享。掌握原子类型和内存序的使用,将为构建高性能并发程序奠定坚实的基础。

推荐阅读: