当前位置: 首页 > article >正文

Modern C++ std::atomic简介

文章目录

    • 为什么选择 std::atomic?
    • std::atomic 的主要特性
      • 1. 支持多种操作
      • 2. 灵活的内存序
      • 3. 支持多种类型
    • 实践篇:std::atomic 的使用场景
      • 环境要求
      • 场景 1:如何用 `std::atomic` 实现线程安全计数器
        • 示例需求
        • 使用 `std::atomic` 的高效实现
      • 场景 2:多线程竞争同一任务
        • 示例需求
        • 示例代码
      • 场景 3: 协调执行阶段
        • 示例需求
        • 示例代码
    • 高级篇:内存序模型
      • 应用场景
    • 总结

在现代高性能多线程编程中,如何高效、安全地处理共享数据是一个关键问题。std::atomic 作为 C++ 标准库提供的一种无锁线程安全工具,因其性能优越和易用性而备受推崇。本文将深入探讨 std::atomic 的特性、使用方法及其在实际开发中的应用场景,帮助读者全面掌握这一工具。


为什么选择 std::atomic?

在多线程环境下,数据共享和修改可能导致竞争条件。传统的 std::mutex 可以通过加锁实现线程安全,但由于其重量级的锁机制,可能带来显著的性能开销。

相比之下,std::atomic 具有以下显著优势:

  1. 线程安全性:所有操作均为原子性,避免数据竞争。
  2. 无锁机制:通过硬件支持的原子指令,消除了线程上下文切换的开销。
  3. 内存序模型:灵活的内存序控制,满足不同场景下的性能与一致性需求。

适用场景包括:

  • 简单变量的多线程读写,如计数器、标志位。
  • 替代频繁加锁的场景,以优化性能。

std::atomic 的主要特性

1. 支持多种操作

  • 基本操作loadstore 实现共享变量的读取和写入。
  • 数学运算fetch_addfetch_sub 提供原子加减功能。
  • 比较并交换compare_exchange_weakcompare_exchange_strong 用于实现条件更新。
  • 高效等待与通知:通过 waitnotify_onenotify_all 实现线程间的高效协作。

2. 灵活的内存序

提供从 memory_order_relaxedmemory_order_seq_cst 的多种内存序控制,可根据场景需求在性能与一致性之间灵活取舍。

3. 支持多种类型

适用于基础类型(如 intbool),也可通过特化支持自定义类型。


实践篇:std::atomic 的使用场景

环境要求

本文中的代码需要编译器支持 C++20 标准. 本文在

  1. GCC 13.2 上面测试通过
  2. Clang 18.1 上面测试通过

场景 1:如何用 std::atomic 实现线程安全计数器

示例需求

实现一个计数器,支持以下操作:

  • Increase():将计数器加 1,并返回更新后的值。
  • IncreaseBy(int):增加指定值,并返回更新后的值。
  • DecreaseBy(int):减少指定值,并返回更新后的值。
  • Get():返回当前计数器的值。
  • Reset():将计数器重置为初始值。
使用 std::atomic 的高效实现
#include <atomic>
#include <iostream>

class Counter {
 public:
  Counter(int id) : counter_(id) {}

  void Reset(int cnt) { counter_.store(cnt); }

  int Get() { return counter_.load(); }

  int Increase() { return counter_++; }

  int IncreaseBy(int cnt) { return counter_.fetch_add(cnt) + cnt; }

  int DecreaseBy(int cnt) { return counter_.fetch_sub(cnt) - cnt; }

 private:
  std::atomic<int> counter_;
};

int main() {
  Counter counter(0);
  counter.Increase();
  counter.IncreaseBy(10);
  counter.DecreaseBy(5);
  std::cout << "Counter: " << counter.Get() << std::endl;

  return 0;
}

在 Compiler Explorer 上查看.

场景 2:多线程竞争同一任务

示例需求

有一些特定的任务需要有一个线程来执行,并且需要保证只有一个线程能够成功。这时,我们可以使用 std::atomic 来实现。

示例代码
#include <atomic>
#include <chrono>
#include <iostream>
#include <random>
#include <thread>
#include <vector>

std::atomic<unsigned long int> leader_id(-1);

void try_to_be_leader() {
  // 生成一个 100 到 1000 毫秒之间的随机睡眠时间
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_int_distribution<> dis(100, 1000);
  int sleep_duration = dis(gen);

  // 休眠随机时间, 模拟线程执行任务
  std::this_thread::sleep_for(std::chrono::milliseconds(sleep_duration));

  auto thread_id = std::this_thread::get_id();
  unsigned long int expected = -1;
  // 尝试成为 leader
  if (leader_id.compare_exchange_strong(
          expected, std::hash<std::thread::id>{}(thread_id))) {
    std::cout << "Thread " << thread_id << " became the leader." << std::endl;
  } else {
    std::cout << "Thread " << thread_id << " failed." << std::endl;
  }
}

int main() {
  constexpr int kNumThreads = 10;
  std::vector<std::jthread> threads;

  for (int i = 0; i < kNumThreads; ++i) {
    threads.emplace_back(try_to_be_leader);
  }

  std::cout << "Leader is: " << leader_id.load() << std::endl;
  return 0;
}

在 Compiler Explorer 上查看.

运行结果

Thread 127785328707264 became the leader.
Thread 127785276278464 failed.
Thread 127785297249984 failed.
Thread 127785307735744 failed.
Thread 127785286764224 failed.
Thread 127785318221504 failed.
Thread 127785339193024 failed.
Thread 127785360164544 failed.
Thread 127785370650304 failed.
Thread 127785349678784 failed.
Leader is: 6834516529000622202

说明compare_exchange_strong 确保只有一个线程能成功设置 leader_id,实现了多线程下的安全竞争。

场景 3: 协调执行阶段

示例需求

在多线程编程中,不同线程之间需要协调工作,常常需要使用标志位来进行通信。例如,一个线程在数据准备完毕后需要通知其他线程开始工作。这时,我们可以使用 std::atomic<bool> 来实现高效的标志位。

示例代码
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

int main() {
  std::atomic<bool> ready{false};

  std::jthread producer([&]() {
    std::cout << "Producer: Preparing data...\n";
    // 模拟数据准备
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 数据准备完毕
    ready.store(true);
    // 通知等待的线程
    ready.notify_one();

    std::cout << "Producer: Data ready.\n";
  });

  std::jthread consumer([&]() {
    // 等待直到 ready 为 true
    ready.wait(false);
    std::cout << "Consumer: Data received.\n";
  });

  return 0;
}

在 Compiler Explorer 上查看.

说明:通过 waitnotify_all,可以高效地实现生产者-消费者模式,避免频繁轮询带来的性能浪费。


高级篇:内存序模型

std::atomic 支持以下内存序模型:

  1. memory_order_relaxed:不保证顺序,性能最高。
  2. memory_order_acquire:保证后续读取可见之前的写入。
  3. memory_order_release:保证之前的写入对其他线程可见。
  4. memory_order_acq_rel:结合获取和释放语义。
  5. memory_order_seq_cst:全局顺序一致性,最强保证。

应用场景

  • 高性能优化:计数器的无序递增使用 memory_order_relaxed
  • 同步机制:锁实现中使用 memory_order_acquire/release
  • 复杂算法:无锁队列、信号处理中平衡性能与一致性。

总结

通过合理使用 std::atomic,可以在性能与一致性之间找到最佳平衡。尽管其适用范围主要限于简单变量的同步,但其高效、灵活的特性使其成为现代 C++ 多线程编程的重要工具。希望本文的深入探讨能够帮助你更好地掌握并应用 std::atomic,提升项目的性能与安全性。


http://www.kler.cn/a/456363.html

相关文章:

  • VUE前端实现防抖节流 Lodash
  • VBA批量插入图片到PPT,一页一图
  • 雷电模拟器安装LSPosed
  • 排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
  • vue3 video 播放rtmp视频?(360浏览器支持)
  • Windows系统下载、部署Node.js与npm环境的方法
  • neo4j无法导入csv文件
  • 渗透测试入门DVWA 教程1:环境搭建
  • 【WRF模拟】最高/最低日气温偏高/偏低的参数调整
  • 【Android】application@label 属性属性冲突报错
  • 潇洒郎:部署Dify, 安装Ollama,Ollama下载模型,Dify配置模型
  • JavaScript甘特图 dhtmlx-gantt
  • 面试场景题系列:设计URL短链
  • 深度学习中的参数初始化
  • Anaconda 安装与虚拟环境创建完整指南
  • jetbrains HTTPS 请求与响应流量分析报告【二】
  • C语言实践中的补充知识 Ⅶ
  • 在国产电脑上运行PDFSAM软件使用pdf分割合并交替混合处理pdf文档
  • 基于 Vant UI + Redisson BitSet 实现签到日历
  • springBoot发布https服务及调用
  • 77、将adaface的mtcnn模型npy文件转成atlas310p模型,并进行推理
  • 【Linux网络编程】第十五弹---传输层深度解析:端口号划分、UDP协议特性与TCP协议全面剖析(含连接管理、流量控制、拥塞控制等)
  • ShenNiusModularity项目源码学习(6:访问控制)
  • 设计一个基于Spring Boot开发的电商网站,部署在阿里云上
  • 【C/C++】C语言编程规范
  • pthread_create概念和使用案例