C++中的無鎖編程

引言

在當今多核處理器普及的時代,并發編程已成為高性能應用程序開發的關鍵技術。傳統的基于鎖的同步機制雖然使用簡單,但往往會帶來性能瓶頸和死鎖風險。無鎖編程(Lock-Free Programming)作為一種先進的并發編程范式,通過避免使用互斥鎖,能夠顯著提高并發程序的性能和可擴展性。本文將深入探討C++中的無鎖編程技術,包括其基本概念、實現方法、常見模式以及實際應用中的注意事項。

無鎖編程的基本概念

無鎖編程是一種不使用互斥鎖而實現線程同步的技術。在無鎖算法中,即使某個線程的執行被掛起,其他線程仍然能夠繼續執行而不會被阻塞。這種特性使得無鎖算法在高并發環境下具有顯著的性能優勢。

無鎖、無等待與無障礙

在討論無鎖編程時,我們通常會涉及以下幾個概念:

  • 無障礙(Obstruction-Free):如果一個線程在其他線程都暫停的情況下能夠在有限步驟內完成操作,則該算法是無障礙的。
  • 無鎖(Lock-Free):如果系統中總有線程能夠取得進展,則該算法是無鎖的。無鎖算法保證系統整體的進展,但不保證每個線程都能取得進展。
  • 無等待(Wait-Free):如果每個線程都能在有限步驟內完成操作,則該算法是無等待的。無等待是最強的保證,它確保每個線程都能取得進展。

無鎖編程的核心在于使用原子操作和內存序來協調線程間的交互,而不是依賴傳統的鎖機制。

C++原子操作

C++11引入了<atomic>庫,提供了對原子操作的支持,這是實現無鎖編程的基礎。

原子類型

std::atomic是一個模板類,可以將基本類型包裝為原子類型:

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>std::atomic<int> counter(0);void increment() {for (int i = 0; i < 1000; ++i) {counter++;  // 原子遞增操作}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.push_back(std::thread(increment));}for (auto& t : threads) {t.join();}std::cout << "Final counter value: " << counter << std::endl;return 0;
}

原子操作函數

除了基本的原子類型外,C++還提供了一系列原子操作函數:

  • std::atomic_load:原子讀取
  • std::atomic_store:原子存儲
  • std::atomic_exchange:原子交換
  • std::atomic_compare_exchange_weak/std::atomic_compare_exchange_strong:比較并交換(CAS操作)

CAS(Compare-And-Swap)操作是無鎖編程中最常用的基本操作之一:

bool cas_example(std::atomic<int>& target, int expected, int desired) {return target.compare_exchange_strong(expected, desired);
}

內存序

在多處理器系統中,內存操作的順序可能會因為編譯器優化和處理器重排序而改變。C++11引入了內存序(Memory Ordering)的概念,允許程序員指定原子操作之間的順序關系。

內存序類型

C++提供了六種內存序選項:

  • memory_order_relaxed:最寬松的內存序,只保證操作的原子性,不提供同步或順序保證。
  • memory_order_consume:讀操作依賴于特定的寫操作。
  • memory_order_acquire:讀操作,獲取當前線程之前的所有寫操作的結果。
  • memory_order_release:寫操作,釋放當前線程之前的所有寫操作的結果。
  • memory_order_acq_rel:同時具有acquire和release語義。
  • memory_order_seq_cst:最嚴格的內存序,提供全序關系。

以下是一個使用不同內存序的示例:

#include <atomic>
#include <thread>
#include <iostream>std::atomic<bool> ready(false);
std::atomic<int> data(0);void producer() {data.store(42, std::memory_order_relaxed);ready.store(true, std::memory_order_release);
}void consumer() {while (!ready.load(std::memory_order_acquire)) {// 自旋等待}int value = data.load(std::memory_order_relaxed);std::cout << "Read value: " << value << std::endl;
}int main() {std::thread t1(producer);std::thread t2(consumer);t1.join();t2.join();return 0;
}

在這個例子中,memory_order_releasememory_order_acquire配對使用,確保consumer線程能夠看到producer線程寫入的數據。

無鎖數據結構

基于原子操作和適當的內存序,我們可以實現各種無鎖數據結構。

無鎖隊列

以下是一個簡化版的無鎖單生產者單消費者隊列實現:

template<typename T>
class LockFreeQueue {
private:struct Node {T data;std::atomic<Node*> next;Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head;std::atomic<Node*> tail;public:LockFreeQueue() {Node* dummy = new Node(T());head.store(dummy);tail.store(dummy);}~LockFreeQueue() {while (Node* node = head.load()) {head.store(node->next);delete node;}}void enqueue(const T& value) {Node* new_node = new Node(value);Node* old_tail = tail.exchange(new_node, std::memory_order_acq_rel);old_tail->next.store(new_node, std::memory_order_release);}bool dequeue(T& result) {Node* current_head = head.load(std::memory_order_acquire);Node* next = current_head->next.load(std::memory_order_acquire);if (!next) {return false;  // 隊列為空}result = next->data;head.store(next, std::memory_order_release);delete current_head;return true;}
};

無鎖棧

以下是一個基于CAS操作的無鎖棧實現:

template<typename T>
class LockFreeStack {
private:struct Node {T data;Node* next;Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head;public:LockFreeStack() : head(nullptr) {}~LockFreeStack() {while (Node* node = head.load()) {head.store(node->next);delete node;}}void push(const T& value) {Node* new_node = new Node(value);Node* old_head = head.load(std::memory_order_relaxed);do {new_node->next = old_head;} while (!head.compare_exchange_weak(old_head, new_node, std::memory_order_release,std::memory_order_relaxed));}bool pop(T& result) {Node* old_head = head.load(std::memory_order_acquire);do {if (!old_head) {return false;  // 棧為空}} while (!head.compare_exchange_weak(old_head, old_head->next,std::memory_order_release,std::memory_order_relaxed));result = old_head->data;delete old_head;return true;}
};

ABA問題及其解決方案

在無鎖編程中,一個常見的問題是ABA問題:一個值從A變為B,再變回A,可能會導致CAS操作誤認為該值沒有被修改過。

ABA問題示例

考慮以下場景:

  1. 線程1讀取一個指針值A
  2. 線程1被掛起
  3. 線程2將指針從A改為B,然后又改回A
  4. 線程1恢復執行,發現指針值仍為A,誤認為沒有發生變化

解決方案:標記指針

一種常見的解決方案是使用標記指針(Tagged Pointer)或版本計數器:

template<typename T>
class TaggedPointer {
private:struct TaggedPtr {T* ptr;uint64_t tag;};std::atomic<TaggedPtr> ptr;public:TaggedPointer(T* p = nullptr) {TaggedPtr tp = {p, 0};ptr.store(tp);}bool compareAndSwap(T* expected, T* desired) {TaggedPtr current = ptr.load();if (current.ptr != expected) {return false;}TaggedPtr newValue = {desired, current.tag + 1};return ptr.compare_exchange_strong(current, newValue);}T* get() {return ptr.load().ptr;}
};

C++11提供了std::atomic<std::shared_ptr<T>>,可以直接用于解決ABA問題,但其性能可能不如手動實現的標記指針。

無鎖編程的實踐建議

1. 謹慎選擇內存序

選擇合適的內存序對于無鎖算法的正確性和性能至關重要。過于嚴格的內存序會導致性能下降,而過于寬松的內存序可能會導致程序錯誤。

2. 考慮內存管理

在無鎖編程中,內存管理是一個復雜的問題。當一個線程釋放一個對象時,其他線程可能仍在使用該對象。解決這個問題的方法包括:

  • 引用計數
  • 危險指針(Hazard Pointers)
  • 紀元計數(Epoch-based reclamation)
  • 讀拷貝更新(Read-Copy-Update,RCU)

3. 全面測試

無鎖算法的正確性難以驗證,因此需要進行全面的測試,包括壓力測試和并發測試。

4. 避免過度使用

無鎖編程不是萬能的。在許多情況下,簡單的鎖機制可能更適合,特別是當并發度不高或性能不是關鍵因素時。

性能對比與分析

為了展示無鎖編程的性能優勢,我們對比了基于鎖的隊列和無鎖隊列在不同線程數下的性能:

// 性能測試代碼
#include <chrono>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <iostream>// 基于鎖的隊列
template<typename T>
class LockedQueue {
private:std::queue<T> q;std::mutex mtx;public:void enqueue(const T& value) {std::lock_guard<std::mutex> lock(mtx);q.push(value);}bool dequeue(T& result) {std::lock_guard<std::mutex> lock(mtx);if (q.empty()) {return false;}result = q.front();q.pop();return true;}
};// 測試函數
template<typename Queue>
void benchmark(Queue& q, int num_threads, int operations_per_thread) {auto start = std::chrono::high_resolution_clock::now();std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.push_back(std::thread([&q, operations_per_thread]() {for (int j = 0; j < operations_per_thread; ++j) {q.enqueue(j);int result;q.dequeue(result);}}));}for (auto& t : threads) {t.join();}auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);std::cout << "Time taken with " << num_threads << " threads: " << duration.count() << " ms" << std::endl;
}

測試結果表明,在高并發場景下,無鎖隊列的性能優勢顯著,特別是當線程數接近或超過CPU核心數時。

總結與展望

無鎖編程作為一種高級并發編程技術,能夠在高并發場景下提供顯著的性能優勢。C++11及后續標準通過提供原子操作和內存序模型,為無鎖編程提供了堅實的基礎。

然而,無鎖編程也面臨著諸多挑戰,包括復雜的內存管理、難以驗證的正確性以及潛在的ABA問題。隨著硬件和編程語言的發展,我們可以期待更多的工具和庫來簡化無鎖編程。

在實際應用中,應當根據具體場景選擇合適的并發策略,無鎖編程并非適用于所有情況。對于并發度不高或對性能要求不嚴格的場景,傳統的基于鎖的同步機制可能是更簡單、更可靠的選擇。

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

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

相關文章

FastGPT 引申:借鑒 FastGPT 基于MySQL + ES 實現知識庫(含表結構以及核心代碼)

文章目錄 FastGPT 引申&#xff1a;借鑒 FastGPT 基于MySQL ES 實現知識庫&#xff08;含表結構以及核心代碼&#xff09;一、整體思路二、存儲結構2.1 MySQL 表結構(1) knowledge_base_dataset(2) knowledge_base_data(3) knowledge_base_index(4) ai_kb_relation 2.2 Elasti…

Python學習(十四)pandas庫入門手冊

目錄 一、安裝與導入二、核心數據結構2.1 Series 類型&#xff08;一維數組&#xff09;2.2 DataFrame 類型&#xff08;二維數組&#xff09; 三、數據讀取與寫入3.1 讀取 CSV 和 Excel 文件3.2 寫入數據 四、數據清洗與處理4.1 處理缺失值4.2 數據篩選4.3 數據排序 五、數據分…

【Python 數據結構 4.單向鏈表】

目錄 一、單向鏈表的基本概念 1.單向鏈表的概念 2.單向鏈表的元素插入 元素插入的步驟 3.單向鏈表的元素刪除 元素刪除的步驟 4.單向鏈表的元素查找 元素查找的步驟 5.單向鏈表的元素索引 元素索引的步驟 6.單向鏈表的元素修改 元素修改的步驟 二、Python中的單向鏈表 ?編輯 三…

第1章:項目概述與環境搭建

第1章&#xff1a;項目概述與環境搭建 學習目標 了解YunChangAction靈感記錄應用的整體架構和功能掌握SwiftUI開發環境的配置方法創建項目基礎結構并理解文件組織方式實現應用的啟動屏幕和基本主題設置 理論知識講解 靈感記錄應用概述 靈感記錄應用是一種專門設計用來幫助…

2025.3.3總結

周一這天&#xff0c;我約了績效教練&#xff0c;主要想了解專業類績效的考核方式以及想知道如何拿到一個更好的績效。其他的崗位并不是很清楚&#xff0c;但是專業類的崗位&#xff0c;目前采取絕對考核&#xff0c;管理層和專家崗采取相對考核&#xff0c;有末尾淘汰。 通過…

FastGPT 源碼:基于 LLM 實現 Rerank (含Prompt)

文章目錄 基于 LLM 實現 Rerank函數定義預期輸出實現說明使用建議完整 Prompt 基于 LLM 實現 Rerank 下邊通過設計 Prompt 讓 LLM 實現重排序的功能。 函數定義 class LLMReranker:def __init__(self, llm_client):self.llm llm_clientdef rerank(self, query: str, docume…

LeetCode 1745.分割回文串 IV:動態規劃(用III或II能直接秒)

【LetMeFly】1745.分割回文串 IV&#xff1a;動態規劃&#xff08;用III或II能直接秒&#xff09; 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/palindrome-partitioning-iv/ 給你一個字符串 s &#xff0c;如果可以將它分割成三個 非空 回文子字符串&#xff0c;…

25年3月5日

1.思維導圖 2.不太會 #include "head.h" int main(int argc, const char *argv[]) {int fdopen("../xiaoxin.bmp","O_RDONLY");if(fd-1)printf("open error");//大小struct stat st;if(stat("…

全球首創!微軟發布醫療AI助手,終結手寫病歷時代

今天凌晨&#xff0c;微軟發布了醫療界首個用于臨床工作流程的AI助手Microsoft Dragon Copilot。 Dragon Copilot是基于語音文本的混合架構&#xff0c;能夠將醫生的語音或臨床口述內容實時轉換為文本。例如&#xff0c;醫生可以通過語音輸入患者的病歷信息、醫囑或診斷結果&a…

[自動駕駛-傳感器融合] 多激光雷達的外參標定

文章目錄 引言外參標定原理ICP匹配示例參考文獻 引言 多激光雷達系統通常用于自動駕駛或機器人&#xff0c;每個雷達的位置和姿態不同&#xff0c;需要將它們的數據統一到同一個坐標系下。多激光雷達外參標定的核心目標是通過計算不同雷達坐標系之間的剛性變換關系&#xff08…

Blazor-路由模板(下)

路由約束 類型約束 我們這里使用{id:int}限制路由&#xff0c;id為int類型&#xff0c;并且路由參數 id 對應的 Id 屬性也必須是 int 類型。我們試試能否正常訪問 page "/demoPage/{id:int}" <h3>demoPage</h3> <h2>路由參數Id&#xff1a;Id&l…

多線程-JUC源碼

簡介 JUC的核心是AQS&#xff0c;大部分鎖都是基于AQS擴展出來的&#xff0c;這里先結合可重入鎖和AQS&#xff0c;做一個講解&#xff0c;其它的鎖的實現方式也幾乎類似 ReentrantLock和AQS AQS的基本結構 AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象隊列…

通過多線程獲取RV1126的AAC碼流

目錄 一RV1126多線程獲取音頻編碼AAC碼流的流程 1.1AI模塊的初始化并使能 1.2AENC模塊的初始化 ???????1.3綁定AI模塊和AENC模塊 ???????1.4多線程獲取每一幀AAC碼流 ???????1.5每個AAC碼流添加ADTSHeader頭部 ???????1.6寫入具體每一幀AAC的…

JVM常用概念之對象初始化的成本

在JVM常用概念之新對象實例化博客中我講到了對象的實例化&#xff0c;主要包含分配&#xff08;TLAB&#xff09;、系統初始化、用戶初始化&#xff0c;而我在JVM常用概念之線程本地分配緩沖區&#xff08;ThreadLocal Allocation Buffer&#xff0c;TLAB&#xff09;博客中也講…

java后端開發day27--常用API(二)正則表達式爬蟲

&#xff08;以下內容全部來自上述課程&#xff09; 1.正則表達式&#xff08;regex&#xff09; 可以校驗字符串是否滿足一定的規則&#xff0c;并用來校驗數據格式的合法性。 1.作用 校驗字符串是否滿足規則在一段文本中查找滿足要求的內容 2.內容定義 ps&#xff1a;一…

AI---DevOps常備工具(?AI-Integrated DevOps Essential Tools)

AI---DevOps常備工具 技術領域正在迅速發展&#xff0c;隨著我們步入 2025 年&#xff0c;有一點是明確的&#xff1a;人工智能&#xff08;AI&#xff09;不再只是一個流行詞&#xff0c;它是每個 DevOps 工程師都需要掌握的工具。隨著云環境的復雜性增加、對更快部署的需求以…

Pytorch中的主要函數

目錄 一、torch.manual_seed(seed)二、torch.cuda.manual_seed(seed)三、torch.rand(*size, outNone, dtypeNone, layouttorch.strided, deviceNone, requires_gradFalse)四、給大家寫一個常用的自動選擇電腦cuda 或者cpu 的小技巧五、torch.version.cuda&#xff1b;torch.bac…

Spring Boot中對接Twilio以實現發送驗證碼和驗證短信碼

Twilio介紹 Twilio是一家提供云通信服務的公司&#xff0c;旨在幫助開發者和企業通過簡單的API實現各種通信功能。以下是Twilio的一些主要特點和服務介紹&#xff1a; 核心功能 短信服務&#xff08;SMS&#xff09;&#xff1a;允許用戶通過API發送和接收短信&#xff0c;支…

VSCode詳細安裝步驟,適用于 Windows/macOS/Linux 系統

以下是 Visual Studio Code (VSCode) 的詳細安裝步驟&#xff0c;適用于 Windows/macOS/Linux 系統&#xff1a; VSCode 的詳細安裝步驟 一、Windows 系統安裝1. 下載安裝包2. 運行安裝程序3. 驗證安裝 二、macOS 系統安裝1. 方法一&#xff1a;官網下載安裝包2. 方法二&#x…

基于PyTorch的深度學習3——基于autograd的反向傳播

反向傳播&#xff0c;可以理解為函數關系的反向傳播。