深入解析 C++11 的 `std::atomic`:误区、性能与实际应用

在这里插入图片描述

在现代 C++ 开发中,std::atomic 是处理多线程同步时的重要工具之一。它通过提供原子操作保证了线程安全,但在实际使用时却隐藏着许多不为人知的陷阱和性能影响。本篇文章将带你深入理解 std::atomic 的使用方式、潜在问题,以及如何正确应用于多线程环境。


为什么需要 std::atomic

在多线程程序中,共享变量的读写可能会发生竞态条件(race condition)。传统的锁(如 std::mutex)可以解决这个问题,但锁的使用会导致性能下降。而 std::atomic 通过底层硬件的支持,实现了高效的原子操作,无需额外加锁。

关键点std::atomic 是 C++11 引入的,用于简化并发编程,同时保证线程安全。


一、误区与注意事项

1. 并非所有操作都是原子的

很多开发者容易误以为 std::atomic<T> 的所有操作都是原子性的,但实际上,只有特定的操作(如加减法、位运算等)是原子性的。对于以下类型的运算,std::atomic 并不支持原子性:

  • 整型的乘法和除法
  • 浮点数的加减乘除

来看一个实际的例子:

std::atomic_int x{1};
x = 2 * x;  // 非原子操作

表面上看,这段代码好像是一个简单的原子操作,但实际上它是以下分步操作的组合:

std::atomic_int x{1};
int tmp = x.load();  // 原子读取
tmp = tmp * 2;       // 普通乘法
x.store(tmp);        // 原子写入

因此,这段代码不能保证线程安全。

如何避免?

推荐使用 std::atomic 提供的专用方法,比如 fetch_addfetch_sub 等。以下是一个对比示例:

std::atomic_int x{1};
x.fetch_add(1);  // 原子操作
x += 1;          // 原子操作
x = x + 1;       // 非原子操作
图解:
线程 1 原子变量 load() 原子读取 乘法操作 store() 原子写入 线程 1 原子变量

2. std::atomic 并非总是无锁的

无锁(lock-free)std::atomic 的重要特性之一,但并非所有 std::atomic 对象都能实现无锁操作。是否无锁依赖于以下因素:

  1. 数据类型的大小

    • 小型数据类型(如 intlong)通常可以无锁操作。
    • 大型结构体(如包含多个成员的结构体)则可能需要锁。
  2. 硬件架构

    • 某些 CPU(如 x86 架构)支持更广泛的无锁原子操作,而其他架构(如 ARM)可能对复杂类型采用加锁机制。

std::atomic 提供了 is_lock_free 方法来检查是否支持无锁操作:

std::atomic<int> a;
std::cout << "Is lock free? " << a.is_lock_free() << std::endl;
结构体示例
struct A { long x; };       // 通常无锁
struct B { long x; long y; };  // 可能无锁
struct C { char s[1024]; };  // 通常需要锁

二、性能与陷阱

使用原子操作一定会带来性能开销,这是因为原子操作涉及硬件的缓存同步机制和内存屏障(Memory Barrier)。

示例:原子操作的性能测试

以下代码比较了使用普通变量和原子变量的性能差异:

#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>// 使用普通变量
int non_atomic_value = 0;// 使用原子变量
std::atomic<int> atomic_value(0);void increment_atomic() {for (int i = 0; i < 100000; ++i) {atomic_value.fetch_add(1);}
}void increment_non_atomic() {for (int i = 0; i < 100000; ++i) {non_atomic_value++;}
}int main() {auto start = std::chrono::high_resolution_clock::now();std::thread t1(increment_atomic);std::thread t2(increment_atomic);t1.join();t2.join();auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);std::cout << "Atomic time: " << duration.count() << "ms\n";std::cout << "Final atomic value: " << atomic_value.load() << "\n";start = std::chrono::high_resolution_clock::now();t1 = std::thread(increment_non_atomic);t2 = std::thread(increment_non_atomic);t1.join();t2.join();end = std::chrono::high_resolution_clock::now();duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);std::cout << "Non-atomic time: " << duration.count() << "ms\n";std::cout << "Final non-atomic value: " << non_atomic_value << "\n";return 0;
}

运行结果分析:

  • 原子操作虽然保证了线程安全,但其耗时通常高于普通变量操作。
  • 非原子变量可能导致数据竞争,结果不可靠。

三、实际应用示例

1. compare_exchange_strong

compare_exchange_strong 是原子操作中的核心,用于实现线程安全的条件更新。其原理可以理解为:

value == expected ? value = new_value : expected = value;
示例代码:
#include <iostream>
#include <atomic>int main() {std::atomic<int> value(0);int expected = 5;int new_value = 11;bool result = value.compare_exchange_strong(expected, new_value);if (result) {std::cout << "Update successful. New value: " << value << "\n";} else {std::cout << "Update failed. Current value: " << value << ", expected was updated to: " << expected << "\n";}return 0;
}

四、总结

std::atomic 是 C++ 多线程编程的重要工具,但在使用中需注意以下几点:

  1. 并非所有操作都具备原子性,需谨慎选择操作方式。
  2. std::atomic 是否无锁依赖于数据类型、硬件架构和内存对齐。
  3. 虽然 std::atomic 提供线程安全,但也会带来一定性能开销。

通过正确使用 std::atomic 提供的原子方法,可以在多线程编程中实现更高效、更可靠的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/62490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

芋道源码,芋道sql,yudao,yudao-vue-pro拒绝割韭菜

芋道的开发指南实际上只需要小小的操作就可以观看啦 为了避免被割韭菜 我们可以使用插件去进行解锁文档 项目地址 otomayss/free-yd (github.com)[这里是图片002]https://github.com/otomayss/free-yd

Mac软件推荐

Mac软件推荐 截图SnipasteXnipBob 快捷启动Raycast 系统检测Stats 解压缩The UnarchiverKeka&#xff08;付费&#xff09; 视频播放IINA 视频下载Downie&#xff08;付费&#xff09; 屏幕刘海TopNotchMediaMate&#xff08;付费&#xff09;NotchDrop&#xff08;付费&#x…

【ETCD】【源码阅读】 深入解析 raftNode.start`函数:Raft 核心启动逻辑剖析

raftNode.start方法 是 etcd 中 Raft 模块的核心启动点&#xff0c;其职责是管理 Raft 状态机的状态变迁、日志处理及集群通信等逻辑。通过对源码的逐行分析&#xff0c;我们将全面揭示其运行机制&#xff0c;探讨其设计背后的分布式系统理念。 函数核心结构 raftNode.start 方…

车站值班员题库

1. 联系用手信号显示十、五、三车距离信号中的“三车”&#xff08;约33m&#xff09;信号时&#xff0c;昼间的显示方式为展开的绿色信号旗单臂平伸下压 &#xff08; 一 &#xff09;次。J442 2. 联系用手信号显示股道号码时&#xff0c;昼间右臂向上直伸&#xff0c…

BI中场战事:国外厂商退,国产厂商进

从沉睡的黄金到经济的新宠&#xff0c;数据要素正上演华丽转身。 近年来&#xff0c;数字经济的长驱向前&#xff0c;离不开数据要素价值释放所带来的持续动力。作为第五大生产要素&#xff0c;数据要素的价值释放需要从数据采集、传输到存储、治理&#xff0c;再到分析和可视…

2024年华中杯数学建模C题基于光纤传感器的平面曲线重建算法建模解题全过程文档及程序

2024年华中杯数学建模 C题 基于光纤传感器的平面曲线重建算法建模 原题再现 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号&#xff0c;其基本原理是当外界环境参数发生变化时&#xff0c…

【LeetCode每日一题】LeetCode 209.长度最小的子数组

LeetCode 209.长度最小的子数组 题目描述 给定一个正整数数组 nums 和一个正整数 target&#xff0c;找出连续子数组的最小长度&#xff0c;使得子数组的和大于或等于 target。如果不存在符合条件的子数组&#xff0c;返回 0。 Java 实现代码 public class Solution {publi…

【openwrt】openwrt-21.02 基于IP地址使用ipset实现策略路由操作说明

openwrt版本信息 DISTRIB_ID=OpenWrt DISTRIB_RELEASE=21.02-SNAPSHOT DISTRIB_REVISION=r0-6bf6af1d5 DISTRIB_TARGET=mediatek/mt7981 DISTRIB_ARCH=aarch64_cortex-a53 DISTRIB_DESCRIPTION=OpenWrt 21.02-SNAPSHOT r0-6bf6af1d5 DISTRIB_TAINTS=no-all busybox override …

【H2O2|全栈】MySQL的基本操作(三)

目录 前言 开篇语 准备工作 案例准备 多表查询 笛卡尔积 等值连接 外连接 内连接 自连接 子查询 存在和所有 含于 分页查询 建表语句 结束语 前言 开篇语 本篇继续讲解MySQL的一些基础的操作——数据字段的查询中的多表查询和分页查询&#xff0c;与单表查询…

从单体到微服务:如何借助 Spring Cloud 实现架构转型

一、Spring Cloud简介 Spring Cloud 是一套基于 Spring 框架的微服务架构解决方案&#xff0c;它提供了一系列的工具和组件&#xff0c;帮助开发者快速构建分布式系统&#xff0c;尤其是微服务架构。 Spring Cloud 提供了诸如服务发现、配置管理、负载均衡、断路器、消息总线…

yarn : 无法加载文件 C:\Users\L\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁

关于执行安装yarn命令后执行yarn -v报错&#xff1a; 先确认执行安装yarn命令是否有误 # 安装yarn npm install yarn -g 终端输入set-ExecutionPolicy RemoteSigned 当然如果yarn -v仍然执行失败&#xff0c;考虑使用管理员方式运行IDEA&#xff0c; 注&#xff1a;如上操作…

centos 常见问题处理

免密登录配置 # 在当前机器下 执行命令 生成 私钥和公钥 ~/.ssh 目录下 ssh-keygen -t rsa # 执行如下命令 把公钥 放到 对应机器上的 ~/.ssh/authorized_keys ssh-copy-id 172.17.68.220 # 如此 两台机器两两配置 centos ssh连接慢 vim /etc/ssh/sshd_config # UseD…

java全栈day12-后端Web实战(IOC+DI)

前言&#xff1a;前面的基础知识了解后进入实战篇&#xff0c;从以下四个方面进行准备 一、开发规范 1.1前后端分离开发 前言回顾 二、Restful风格 引言&#xff1a;前端与后端在进行交互的时候&#xff0c;所使用的url风格叫Restful。 2.1概述 小结 2.2环境准备 2.2.1apif…

链式设计模式——装饰模式和职责链模式

一、装饰模式 1、概述 动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰模式比生成子类更为灵活。 ConcreteComponent &#xff1a;是定义了一个具体的对象&#xff0c;可以给这个对象添加一些职责&#xff1b;Decorator &#xff1a;装饰抽象…

Cmake+基础命令

一、版本要求&#xff1a; 检查 cmake 版本号的最低要求&#xff0c;不满足条件时报错。 cmake_minimum_required(VERSION <version>)参数&#xff1a; version&#xff1a;最低要求的版本号 例子&#xff1a; # 最低要求安装3.21版本的cmake cmake_minimum_required…

Java——容器(单例集合)(上)

一 容器介绍 容器&#xff0c;是用来容纳物体、管理物体。生活中,我们会用到各种各样的容器。如锅碗瓢盆、箱子和包等 程序中的“容器”也有类似的功能&#xff0c;用来容纳和管理数据。比如&#xff0c;如下新闻网站的新闻列表、教育网站的课程列表就是用“容器”来管理 视频…

word poi-tl 表格功能增强,实现表格功能垂直合并

目录 问题解决问题poi-tl介绍 功能实现引入依赖模版代码效果图 附加&#xff08;插件实现&#xff09;MergeColumnData 对象MergeGroupData 类ServerMergeTableData 数据信息ServerMergeTablePolicy 合并插件 问题 由于在开发功能需求中&#xff0c;word文档需要垂直合并表格&…

OpenCV相机标定与3D重建(11)机器人世界手眼标定函数calibrateRobotWorldHandEye()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算机器人世界/手眼标定&#xff1a; w T b _{}^{w}\textrm{T}_b w​Tb​ 和 c T g _{}^{c}\textrm{T}_g c​Tg​。 cv::calibrateRobotWorldHa…

GPT系列模型简要概述

GPT-1&#xff1a;&#xff08;0.117B参数量&#xff0c;0.8B words预训练数据) 动机&#xff1a; 在RNN和Transformer之间&#xff0c;选择了后者。 和《All your need is Attention》翻译模型的Encoder-Decoder架构相比&#xff0c;只保留Decoder&#xff0c;因此去掉了Cross…

汽车升级到底应不应该设置“可取消“功能

最近&#xff0c;汽车OTA&#xff08;Over-the-Air&#xff09;升级频频成为车主讨论的热点。有些车主反映&#xff0c;一些升级增加了实用功能&#xff0c;而另一些却让体验变得复杂甚至带来不便。于是&#xff0c;大家不禁发问&#xff1a;汽车升级功能究竟应不应该允许“可取…