深入理解 C++ volatile 與 atomic:五大用法解析 + 六大高頻考點

一、volatile

volatile是C++中一個非常重要的關鍵字。volatile關鍵字告訴編譯器,被修飾的變量可能會在程序控制之外被改變,因此編譯器不能對該變量的訪問進行優化。什么意思呢?現代處理器架構中,有寄存器,L1緩存,L2 緩存,L3 緩存,內存這種架構,可以發現,為了提高訪問速度,會將計算的中間變量直接保存在緩存中,再慢慢刷新到內存。

在這里插入圖片描述

這么做當然提高了訪問速度,但是!但是!但是!數據被修改后是不能直接反饋到內存的,這樣做會存在一些問題。

1.1、volatile的基本含義

volatile關鍵字告訴編譯器,被修飾的變量可能會在程序控制之外被改變,因此編譯器不能對該變量的訪問進行優化。

volatile int flag = 0;
  • 讀取可見性:每次讀取volatile變量都必須從內存中讀取,不能使用寄存器和三級緩存中的緩存值
  • 寫入可見性:每次寫入volatile變量都必須立即寫入內存
  • 順序性:對volatile變量的操作不會被編譯器重排序(但處理器仍可能重排序)

1.2、volatile的主要用途

1.2.1、硬件寄存器訪問

在嵌入式系統中,硬件寄存器通常映射到內存地址,其值會由硬件改變:

volatile unsigned int *reg = (unsigned int *)0x1234;
*reg = 1;       // 寫入寄存器
int val = *reg; // 必須從寄存器讀取,不能使用緩存值

1.2.2、多線程共享變量

在C++11之前,volatile有時被用來實現線程間共享變量(注意:這不是標準推薦的做法):

volatile bool ready = false;// 線程1
void producer() {ready = true; // 告訴消費者數據已準備好
}// 線程2
void consumer() {while(!ready); // 等待數據準備好
}

1.2.3、信號處理程序中的變量

信號處理程序(Signal Handler)是操作系統提供的一種異步事件處理機制,用于響應系統或程序運行時發生的各種事件(稱為"信號")。它是Unix/Linux系統編程和C/C++底層編程中的重要概念。

信號是操作系統向進程發送的異步通知,用于通知進程發生了某種事件。常見信號包括:

  • SIGINT (2):終端中斷信號(通常是Ctrl+C)
  • SIGSEGV (11):段錯誤(非法內存訪問)
  • SIGTERM (15):終止信號
  • SIGALRM (14):定時器信號
  • SIGUSR1 (10)/SIGUSR2 (12):用戶自定義信號

當變量可能在信號處理程序中被修改時:

volatile sig_atomic_t signal_received = 0;void handler(int) {signal_received = 1;
}

在信號處理程序中使用volatile關鍵字修飾變量是為了解決編譯器優化可能導致的可見性問題,確保信號處理程序與主程序之間能夠正確通信。

1.2.4、volatile與const的結合

volatile可以和const結合使用,表示變量在程序內不可修改,但可能被外部修改:

const volatile int hardware_clock = 0x1234;

1.3、volatile的局限性

  • 不是線程安全的volatile不提供原子性保證,不能替代std::atomic

  • 不保證內存順序:不提供內存屏障或順序一致性

  • 不阻止處理器重排序:僅阻止編譯器優化,不限制處理器行為

二、std::atomic

std::atomic是C++11引入的模板類,為多線程編程提供了真正的原子操作支持,解決了volatile在多線程環境中的不足。

2.1、定義

std::atomic提供了一種線程安全的方式來訪問和修改共享數據:

#include <atomic>
std::atomic<int> counter(0); // 原子整型變量

2.1.1、基本原子操作

std::atomic<int> val;val.store(42);                                     // 原子存儲
int x = val.load();                                // 原子加載
int y = val.exchange(43);                          // 原子交換
bool success = val.compare_exchange_strong(expected = x, desired = 44); // 比較交換,比較原子變量的當前值是否與 expected 相等// 如果相等,則將原子變量的值設置為 desired,并返回 true// 如果不相等,則將 expected 更新為原子變量的當前值,并返回 false

2.1.2、原子算數運算(僅限整型)

std::atomic<int> count(0);count.fetch_add(1);      // 原子加
count.fetch_sub(1);      // 原子減
count++;                 // 等價于fetch_add(1)

2.1.3、基本原子操作(僅限整型)

std::atomic<int> flags(0);flags.fetch_and(0x0F);   // 原子與
flags.fetch_or(0x01);    // 原子或

2.2、內存順序詳解

C++原子操作允許指定內存順序,控制操作的可見性和順序性:

enum memory_order {memory_order_relaxed,   // 最寬松,只保證原子性memory_order_consume,   // 數據依賴順序memory_order_acquire,   // 獲取操作memory_order_release,   // 釋放操作memory_order_acq_rel,   // 獲取-釋放操作memory_order_seq_cst    // 順序一致性(默認)
};

2.2.1、 memory_order_seq_cst (順序一致性)

  • 最強保證:所有線程看到的內存操作順序一致
  • 性能開銷:最大
  • 默認選擇:如果不確定就用這個

舉個順序一致性的例子:

std::atomic<int> x(0), y(0);// 線程1
x.store(1, std::memory_order_seq_cst);  // A
y.store(1, std::memory_order_seq_cst);  // B// 線程2
int r1 = y.load(std::memory_order_seq_cst);  // C
int r2 = x.load(std::memory_order_seq_cst);  // D

可能的執行順序:

  • A → B → C → D (r1=1, r2=1)
  • A → C → B → D (r1=0, r2=1)
  • C → D → A → B (r1=0, r2=0)

不會出現 r1=1 且 r2=0 的情況,因為這違反順序一致性。每個seq_cst操作都相當于一個全內存屏障(full memory fence),阻止屏障前后的任何內存操作跨越屏障

2.2.2、memory_order_acquire (獲取語義)

  • 適用場景:讀操作(加載)
  • 保證:當前操作之后的所有內存訪問(包括非原子操作)不會被重排序到它前面
  • 效果:獲取其他線程釋放的內容

2.2.3、memory_order_release (釋放語義)

  • 適用場景:寫操作(存儲)

  • 保證:當前操作之前的所有寫操作不會被重排序到它后面

  • 效果:釋放內容給其他獲取的線程

舉個獲取語義與釋放語義的例子:

std::atomic<bool> ready(false);
int data = 0;void producer() {data = 42;                                    // (1) 非原子寫入ready.store(true, std::memory_order_release); // (2) 原子釋放存儲
}void consumer() {while (!ready.load(std::memory_order_acquire)) { // (3) 原子獲取加載// 忙等待}assert(data == 42);                              // (4) 保證成立
}

2.2.4、memory_order_acq_rel (獲取-釋放)

  • 適用場景:讀-修改-寫操作(如fetch_add)
  • 保證:同時具有acquire和release語義,保證操作之后的所有內存訪問不會被重排序到它前面,保證操作之前的所有內存訪問不會被重排序到它后面

2.2.5、memory_order_consume (消費語義)

  • 類似acquire但更弱:只保證依賴該加載操作的數據不被重排序
  • 較少使用:多數情況下用acquire更安全

2.2.6、memory_order_relaxed (寬松順序)

  • 最弱保證:只保證原子性,不保證順序
  • 適用場景:計數器等不需要同步的場景

2.3、std::atomic 的局限性

  • 原子操作不是免費的,比非原子操作慢
  • 復雜的同步問題可能需要結合其他同步機制(如互斥鎖)

三、考點

3.1、volatilestd::atomic 的區別

特性volatileatomic
線程安全不保證保證
原子性不保證保證
內存順序可控制
適用場景硬件寄存器、信號處理多線程同步

volatile僅保證:

  • 編譯器不會優化掉對變量的訪問
  • 每次訪問都從內存讀取/寫入

但不保證:

  • 操作的原子性
  • 多線程環境下的可見性和順序性

3.2、什么是原子操作,為什么需要原子操作

原子操作是指在多線程或并發環境中不可分割的操作,它要么完全執行,要么完全不執行,不會被其他線程或進程中斷的操作。

原子操作主要解決并發編程中的競態條件(Race Condition)問題:

  1. 保證操作的完整性:防止操作被其他線程中斷導致數據不一致
  2. 避免競態條件:確保共享資源的正確訪問
  3. 實現線程安全:不需要使用鎖的情況下保證線程安全
  4. 提高性能:相比鎖機制,原子操作通常有更高的性能

3.3、C++11中的 std::atomic 提供了哪些基本原子類型?

C++提供了以下基本原子類型:

std::atomic<bool>
std::atomic<char>
std::atomic<int>
std::atomic<long>
std::atomic<long long>
std::atomic<bool>
std::atomic<char>
std::atomic<unsigned int>
std::atomic<unsigned long>
std::atomic<unsigned long long>

3.4、請用 std::atomic 實現一個簡單的自旋鎖

class SpinLock {std::atomic<bool> flag{false};
public:void lock() {while(flag.exchange(true, std::memory_order_acquire)) {// 自旋等待}}void unlock() {flag.store(false, std::memory_order_release);}
};

3.5、原子操作和互斥鎖(mutex)有什么區別?各自適用什么場景?

特性原子操作互斥鎖
實現方式無鎖基于鎖
阻塞非阻塞可能阻塞
適用范圍簡單數據類型復雜操作/多變量
性能更高較低
死鎖風險
中斷安全性安全不安全

適用場景:

  • 原子操作:計數器、標志位、簡單狀態等
  • 互斥鎖:復雜數據結構、需要保護多個變量的操作等

3.6、volatile能否保證操作的原子性?為什么?

不能。volatile只能保證內存可見性(每次讀取都從內存獲取最新值),但不能保證操作的原子性

例如,volatile int i = 0; i++;這樣的操作在多線程環境下仍然是不安全的,因為i++實際上是"讀取-修改-寫入"三個步驟的組合操作,可能被其他線程中斷。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/84946.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/84946.shtml
英文地址,請注明出處:http://en.pswp.cn/web/84946.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

跨主機管理Docker容器化應用的操作與技巧

哈嘍&#xff0c;大家好&#xff0c;我是左手python&#xff01; 環境準備與 Docker 安裝 在開始跨主機管理 Docker 容器化應用之前&#xff0c;需要確保所有主機上都安裝了 Docker 引擎&#xff0c;并且這些主機之間可以通過 SSH 協議進行通信。本節將詳細介紹環境準備和 Doc…

編程實踐:sigmastar330 調用IVE圖像處理加速

說明:本專欄文章有兩種解鎖方案 1:付費訂閱,暢享所有文章 2:免費獲取,點擊下方鏈接,關注,自動獲取免費鏈接 https://free-img.400040.xyz/4/2025/04/29/6810a50b7ac8b.jpg 主題:利用IVE進行圖像處理加速 Sigmastar 支持的硬件操作,基本都在:mi_ive.h 文件中,本文…

Nginx+PHP+MySQL安裝參考

NginxPHPMySQL安裝參考 CentOS7環境 配置CentOS7網絡&#xff1a; CentOS(最小安裝)默認是不打開網絡的 啟動網絡 vi打開&#xff1a;/etc/sysconfig/network-scripts/ifcfg-ens33 文件 將 “ONBOOT:no”屬性修改為&#xff1a;“ONBOOT:yes” 重啟網絡服務 # sudo service …

JavaScript中的反射魔法:揭秘Reflect對象的核心方法(下)

JavaScript中的Reflect對象&#xff1a;高級方法解析&#xff08;下&#xff09; 在JavaScript中&#xff0c;Reflect對象不僅提供了基礎的對象操作方法&#xff08;如get、set等&#xff09;&#xff0c;還包含了許多高級API&#xff0c;用于更精細地控制對象行為。本文將繼續…

【數字人開發】Unity+百度智能云平臺實現長短文本個性化語音生成功能

一、創建自己的應用 百度智能云控制臺網址&#xff1a;https://console.bce.baidu.com/ 1、創建應用 2、獲取APIKey和SecretKey 3、Api調試 調試網址&#xff1a;https://console.bce.baidu.com/support/?timestamp1750317430400#/api?productAI&project%E8%AF%AD%E9%…

銀河麒麟 | ubuntu 搭建屬于自己的郵件服務器

目錄 遇權不絕就轉root 更新系統 安裝 Postfix 配置 Postfix 重啟 Postfix 安裝 Dovecot 配置 Dovecot 編輯 Dovecot 的 IMAP 配置文件 編輯 Dovecot 的用戶認證配置文件 編輯 Dovecot 的服務配置文件 重啟 Dovecot 安裝發送郵箱功能 發送郵件 測試 遇權不絕就轉…

嵌入式通信協議框架的四層架構設計與實現

文章目錄 一、硬件抽象層&#xff1a;數據收發的基石1.1 設計要點1.2 代碼示例 二、協議管理層&#xff1a;智能路由中樞2.1 設計要點2.2 代碼示例 三、協議處理層&#xff1a;協議具體實現3.1 設計要求3.2代碼示例3.2.1 協議公共定義3.2.2 協議一設計3.2.3 協議二設計 四、應用…

RA信號處理

ra_snr_gui.m 作用&#xff1a;統計不同信噪比下&#xff0c;五種信號的峰值旁瓣比RA和低高頻均值比RM&#xff0c;繪制結果&#xff0c;參考圖3.11和3.12 DFCW_RA_SNR.m 作用&#xff1a;產生正交離散頻率編碼信號&#xff0c;并計算峰值旁瓣比RA和低高頻均值比 RM LFM_RA_S…

【go的測試】單測之gomock包與gomonkey包

目錄 使用gomock包 1. 安裝mockgen 2. 定義接口 3. 生成mock文件 4. 在單測中使用mock的函數 5. gomock 包的使用問題 使用gomonkey包 1. mock 一個包函數 2. mock 一個公有成員函數 3. mock 一個私有成員函數 使用gomock包 1. 安裝mockgen go get -u github.com/go…

html實現登錄與注冊功能案例(不寫死且只使用js)

目錄 案例需求 實現思路 代碼參考 login.html register.html 運行效果 升級思路 案例需求 需要一個登錄界面和注冊頁面實現一個較為完整的登錄注冊功能 1.登錄界面沒有登錄限制需求&#xff08;降低難度&#xff09;&#xff0c;實現基本的登錄判斷需求&#xff0c;彈窗…

PHP is the best language.

PHP很好寫。 眾所周知Python很好寫&#xff0c;Python 也能開發 Web 應用&#xff0c;但和 PHP 相比&#xff0c;在“直接處理網頁”這件事上&#xff0c;PHP 更加貼近底層和原生。 想快速搭建原型或者 B 端后臺工具&#xff0c;不妨用 PHP Laravel 來搞&#xff0c;真的很香…

Mybatis-Plus 在 getOne() 的時候要加上 .last(“limit 1“)

1.先寫結論: 1.為了確保 SQL 查詢只返回一條記錄&#xff08;當查詢返回多條時會報錯->多為代碼本身問題&#xff09;。 2.防止數據庫執行全表掃描 3.參考網址&#xff1a;問題記錄&#xff1a;MyBatis-Plus 中 ServiceImpl 類的 getOne_mybatis_無他&唯手熟爾-2048…

C語言:二分搜索函數

一、二分搜索基本概念 二分搜索&#xff08;Binary Search&#xff09;是一種在有序數組中查找特定元素的高效算法&#xff0c;時間復雜度為O(log n)。 基本特點&#xff1a; 僅適用于有序數組&#xff08;升序或降序&#xff09; 每次比較將搜索范圍減半 比線性搜索(O(n))…

[前端AI]LangChain.js 和 Next.js LLM構建——協助博客撰寫和總結助手

LangChain.js 和 Next.js LLM 后端應用于協助博客撰寫和總結領域是一個非常實用的方向&#xff01;這涉及到理解和處理文本內容&#xff0c;并生成新的、有結構的信息。 根據您之前提供的代碼和需求&#xff0c;我們可以在此基礎上進行更具針對性的功能規劃和技術實現。 博客…

用 GitHub Issues 做任務管理和任務 List,簡單好用!

說實話&#xff0c;我平時也是一個人寫代碼&#xff0c;每次開完會整理任務最麻煩&#xff1a; 一堆事項堆在聊天里、文檔里&#xff0c;或者散落在郵件里…… 為了理清這些&#xff0c;我通常會做一份 List&#xff0c;標好優先級&#xff0c;再安排到每日的工作里 雖然這個…

每日算法刷題Day35 6.22:leetcode枚舉技巧枚舉中間2道題,用時1h

枚舉中間 對于三個或者四個變量的問題&#xff0c;枚舉中間的變量往往更好算。 為什么&#xff1f;比如問題有三個下標&#xff0c;需要滿足 0≤i<j<k<n&#xff0c;對比一下&#xff1a; 枚舉 i&#xff0c;后續計算中還需保證 j<k。 枚舉 j&#xff0c;那么 i 和…

【教學類-18-06】20250623蒙德里安黑白七款合并WORD(500張、無學號)

背景需要 客戶買了蒙德里安黑白格子7種尺寸,但是不需要學號方塊,并指定要WORD 設計思路 【教學類-18-05】20241118正方形手工紙(蒙德里安-風格派-紅黃藍黑白)-CSDN博客文章瀏覽閱讀1.3k次,點贊29次,收藏18次。【教學類-18-05】20241118正方形手工紙(蒙德里安-風格派-紅…

langchain--(4)

7 Embedding文本向量化 Embedding文本向量化是一種將非結構化文本轉化為低維、連續數值向量的技術,旨在通過數學方式捕捉文本的語義、語法或特征信息,從而讓機器更高效地處理語言任務。其核心思想源于流形假設(Manifold Hypothesis),即認為高維原始數據(如文本)實際隱含…

DMDRS部署實施手冊(ORACLE=》DM)

DMDRS部署實施手冊&#xff08;ORACLE》DM&#xff09; 1 同步說明2 DMDRS安裝3 數據庫準備3.1 源端準備3.1.1 開啟歸檔日志和附加日志3.1.2 關閉回收站3.1.3 創建同步用戶 3.2 目標準備3.2.1 創建同步用戶 4 DMDRS配置4.1 源端配置4.2 目標配置 5 DMDRS啟動5.1 啟動源端服務5.…

十(1)作業:sqli-labs重點關卡

參考文章&#xff1a;詳細sqli-labs&#xff08;1-65&#xff09;通關講解-CSDN博客 第1關&#xff1a; 輸入 &#xff1a; ?id3 輸入 &#xff1a; ?id2 當輸入的數字不同&#xff0c;頁面的響應也不同&#xff0c;說明&#xff0c;輸入的內容被帶入到數據庫里查詢了 輸…