深入解析 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.pswp.cn/web/62490.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62490.shtml
英文地址,請注明出處:http://en.pswp.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;汽車升級功能究竟應不應該允許“可取…