Modern C++ std::atomic简介

server/2024/12/29 11:08:15/

文章目录

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

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


atomic_6">为什么选择 std::atomic?

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

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

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

适用场景包括:

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

atomic__23">std::atomic 的主要特性

1. 支持多种操作

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

2. 灵活的内存序

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

3. 支持多种类型

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


atomic__42">实践篇:std::atomic 的使用场景

环境要求

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

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

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

示例需求

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

  • Increase():将计数器加 1,并返回更新后的值。
  • IncreaseBy(int):增加指定值,并返回更新后的值。
  • DecreaseBy(int):减少指定值,并返回更新后的值。
  • Get():返回当前计数器的值。
  • Reset():将计数器重置为初始值。
atomic__63">使用 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;// 尝试成为 leaderif (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 为 trueready.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.ppmy.cn/server/154184.html

相关文章

深度学习blog-卷积神经网络(CNN)

卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种广泛应用于计算机视觉领域&#xff0c;如图像分类、目标检测和图像分割等任务中的深度学习模型。 1. 结构 卷积神经网络一般由以下几个主要层组成&#xff1a; 输入层&#xff1a;接收…

Vue3响应式:Proxy设计原理解析

# Vue3响应式:Proxy设计原理解析 了解Proxy 在Vue3中&#xff0c;由于Proxy的设计原理&#xff0c;使得其响应式系统更加灵活和高效。那么什么是Proxy呢&#xff1f;Proxy是ES6新增的特性&#xff0c;可以用来自定义对象的操作。通过Proxy&#xff0c;我们可以重写对象的默认行…

【每日学点鸿蒙知识】屏幕高度、证书签名、深色模式对上架影响、Taskpool上下文、List触底加载更多

1、HarmonyOS 关于屏幕高度&#xff1f; display.getDefaultDisplaySync 可以获取到整个屏幕的高度&#xff0c; 那顶部的状态栏和底部的安全区高度 怎么获取&#xff1f; 可以在EntryAbility里获取并存储,获取到的高度是px&#xff0c;所以用px2vp&#xff08;&#xff09;转…

【C++ 真题】P5733 【深基6.例1】自动修正

【深基6.例1】自动修正 题目描述 大家都知道一些办公软件有自动将字母转换为大写的功能。输入一个长度不超过 100 100 100 且不包括空格的字符串。要求将该字符串中的所有小写字母变成大写字母并输出。 输入格式 输入一行&#xff0c;一个字符串。 输出格式 输出一个字符…

基于人工智能时代政务智慧转型的实现前景初探

去年6月&#xff0c;我有幸聆听了由华政公共管理与政治学院精心组织的2019年MPA研究生高端论坛&#xff0c;上午场&#xff1a;由董海军&#xff08;中共上海市委机构编制委员会办公室处长&#xff09;主讲的深化机构改革的探索与实践&#xff0c;以及下午场&#xff1a;由束金…

小程序基础 —— 02 微信小程序账号注册

微信小程序账号注册 小程序开发与网页开发不一样&#xff0c;在开始微信小程序开发之前&#xff0c;需要访问微信公众平台&#xff0c;注册一个微信小程序账号。 有了小程序的账号以后&#xff0c;才可以开发和管理小程序&#xff0c;后续需要通过该账号进行开发信息的设置、…

python实战项目55:多线程爬取笔趣阁小说

python实战项目55:多线程爬取笔趣阁小说 一、明确需求二、单本小说下载三、使用concurrent.futures线程池模块下载并实现文件合并四、使用threading模块实现多线程下载并合并文件一、明确需求 需求是使用多线程爬取笔趣阁网站小说的所有章节内容并保存,多线程分别使用了conc…

服务器反应慢,秒杀设计

目录 服务器反应慢 线上服务器cup飙升&#xff0c;如果定位Java代码&#xff1f; 服务器变慢如何诊断处理&#xff1f; 线上接口负载剧增&#xff0c;快扛不住了&#xff0c;解决方法是什么&#xff1f; 秒杀设计 从全局角度如何设计一个秒杀系统 秒杀活动里遇到的问题 …