深入理解C++中的鎖

目錄

1.基本互斥鎖(std::mutex)

2.遞歸互斥鎖(std::recursive_mutex)

3.帶超時機制的互斥鎖(std::timed_mutex)

4.帶超時機制的遞歸互斥鎖(std::recursive_timed_mutex)

5.共享互斥鎖也叫讀寫鎖(std::shared_mutex)

6.帶超時機制的共享互斥鎖(std::shared_timed_mutex)

7.自旋鎖

8.總結


1.基本互斥鎖(std::mutex)

含義: std::mutex是最基本的互斥鎖,主要用于保護臨界區,確保同一時間只有一個線程可以訪問共享資源。

使用場景: 當需要保護共享資源不被多個線程同時修改時使用。

特點:簡單易用,適用于大多數場景;不能遞歸鎖定,同一線程多次嘗試鎖定會導致死鎖。

以下是一個簡單的示例,展示了如何使用?std::mutex?來保護共享數據:

#include <iostream>  
#include <thread>  
#include <mutex>  std::mutex mtx;     //全局互斥鎖
int shared_data = 0;   //共享數據void increment_shared_data(int n) {  for (int i = 0; i < n; ++i) {  std::lock_guard<std::mutex> lock(mtx);  ++shared_data;  }  
}  int main() {  std::thread t1(increment_shared_data, 1000);  std::thread t2(increment_shared_data, 1000);  t1.join();  t2.join();  std::cout << "Shared data: " << shared_data << std::endl;  return 0;  
}

?這個程序創建了2個線程,每個線程嘗試對counter增加10000次。通過使用std::mutex, 我們確保每次只有一個線程可以增加計數器,避免了數據競爭。

2.遞歸互斥鎖(std::recursive_mutex)

含義std::recursive_mutex允許同一線程多次獲取鎖而不會發生死鎖,這對于遞歸函數或需要多次鎖定的場景非常有用。

使用場景: 在遞歸函數中需要多次獲取同一個鎖的情況。

特點:適用于遞歸調用和需要多次鎖定的場景;需要注意避免濫用,因為遞歸鎖的使用會增加鎖定次數的復雜性。

示例如下:

#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex rmtx;void recursive_function(int depth) {rmtx.lock();std::cout << "Depth: " << depth << std::endl;if (depth > 0) {recursive_function(depth - 1);}rmtx.unlock();
}int main() {std::thread t(recursive_function, 5);t.join();return 0;
}

這段代碼在遞歸函數recursive_function中使用std::recursive_mutex。每次調用都會嘗試加鎖,由于使用的是遞歸互斥鎖,同一線程可以多次成功獲取鎖。

3.帶超時機制的互斥鎖(std::timed_mutex)

含義std::timed_mutexstd::mutex的基礎上增加了超時功能,允許線程在指定時間內嘗試獲取鎖,如果在超時時間內未成功獲取鎖,則返回失敗。

使用場景: 當你不希望線程因等待鎖而無限期阻塞時使用。

特點:適用于需要設置鎖獲取超時時間的場景;提供try_lock_fortry_lock_until兩種超時嘗試獲取鎖的方法。

示例如下:

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <chrono>  std::timed_mutex mtx;  void try_lock_function() {  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Lock acquired!\n";  // 執行受保護的操作  std::this_thread::sleep_for(std::chrono::seconds(2)); // 模擬耗時操作  mtx.unlock(); // 顯式解鎖  } else {  std::cout << "Failed to acquire lock within timeout.\n";  }  
}  int main() {  std::thread t1(try_lock_function);  std::thread t2(try_lock_function);  t1.join();  t2.join();  return 0;  
}

在這個例子中,兩個線程都嘗試在 1 秒內獲取鎖。由于互斥鎖在同一時刻只能被一個線程持有,因此至少有一個線程將無法在超時時間內獲取鎖,并輸出相應的消息。

4.帶超時機制的遞歸互斥鎖(std::recursive_timed_mutex)

含義std::recursive_timed_mutex結合了std::recursive_mutexstd::timed_mutex的特性,支持遞歸鎖定和超時機制。

使用場景: 適用于需要遞歸鎖定資源,并且希望能夠設置嘗試獲取鎖的超時時間的場景。這在需要防止線程在等待鎖時無限阻塞的復雜遞歸調用中特別有用。

特點:適用于遞歸調用和需要超時機制的場景;提供超時嘗試獲取遞歸鎖的方法。

示例如下:

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <chrono>  std::recursive_timed_mutex mtx;  void recursive_lock_function() {  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Lock acquired!\n";  // 遞歸鎖定  if (mtx.try_lock_for(std::chrono::seconds(1))) {  std::cout << "Recursive lock acquired!\n";  mtx.unlock(); // 釋放遞歸鎖  } else {  std::cout << "Failed to acquire recursive lock within timeout.\n";  }  // ... 執行受保護的操作  mtx.unlock(); // 釋放原始鎖  } else {  std::cout << "Failed to acquire lock within timeout.\n";  }  
}  int main() {  std::thread t1(recursive_lock_function);  std::thread t2(recursive_lock_function);  t1.join();  t2.join();  return 0;  
}

請注意,由于?std::recursive_timed_mutex?允許遞歸鎖定,上面的示例中展示了如何在已經持有鎖的情況下再次嘗試獲取鎖(盡管在這個特定示例中,第二次嘗試獲取鎖是多余的,因為我們已經持有鎖了)。然而,在實際情況中,遞歸鎖定可能用于更復雜的場景,其中函數可能會遞歸調用自己,并且每個遞歸調用都需要訪問受保護的數據。

5.共享互斥鎖也叫讀寫鎖(std::shared_mutex)

含義std::shared_mutex允許多個線程同時讀取,但只有一個線程可以寫入。這在讀多寫少的場景下非常有用。

使用場景: 適用于讀操作遠多于寫操作的情況。

特點:適用于讀多寫少的場景;讀操作和寫操作使用不同的鎖定機制。

示例如下:


#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex shmtx;void read_shared(int id) {std::shared_lock<std::shared_mutex> lock(shmtx); // 共享鎖std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}void write_shared(int id) {std::unique_lock<std::shared_mutex> lock(shmtx); // 獨占鎖std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}int main() {std::thread readers[5], writer(write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}

輸出結果可能會有所不同,因為讀寫順序由操作系統的線程調度決定。本例中,一個寫線程在修改數據,多個讀線程在同時讀數據。通過std::shared_mutex,我們允許多個讀操作同時進行,但寫操作是獨占的。

6.帶超時機制的共享互斥鎖(std::shared_timed_mutex)

含義std::shared_timed_mutex?是 C++ 標準庫中的一個同步原語,它結合了?std::shared_mutex(共享互斥鎖)和超時機制的特性。std::shared_mutex?允許多個線程同時以共享模式持有鎖(即讀取操作可以并發執行),但每次只有一個線程能以獨占模式持有鎖(即寫入操作是互斥的)。通過添加超時機制,std::shared_timed_mutex?允許線程嘗試以共享模式或獨占模式獲取鎖,并設置一個超時時間,如果在這段時間內未能成功獲取鎖,則可以放棄并繼續執行其他操作。

使用場景:當你不希望線程因等待鎖而無限期阻塞時使用。

特點:適用于讀多寫少且需要超時機制的場景;提供超時嘗試獲取共享鎖的方法。

示例如下:

#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>std::shared_timed_mutex shtmmtx;void try_read_shared(int id) {if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock_shared();} else {std::cout << "Thread " << id << " could not read" << std::endl;}
}void try_write_shared(int id) {if (shtmmtx.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock();} else {std::cout << "Thread " << id << " could not write" << std::endl;}
}int main() {std::thread readers[5], writer(try_write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(try_read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}

7.自旋鎖

含義:在C++中,自旋鎖(spinlock)是一種低級的同步機制,用于保護共享資源,防止多個線程同時訪問。與互斥鎖(mutex)不同,當自旋鎖被鎖定時,嘗試獲取鎖的線程會不斷循環檢查鎖是否可用,而不是進入睡眠狀態等待鎖被釋放。這意味著,自旋鎖在等待時間很短的情況下是非常有效的,但如果等待時間過長,會導致CPU資源的浪費。

C++標準庫本身并不直接提供自旋鎖的實現,但你可以使用<atomic>庫中的原子操作來手動實現一個自旋鎖,或者使用特定平臺提供的API(如Windows的SRWLOCK或POSIX的pthread_spinlock_t)。

使用場景:自旋鎖適用于鎖持有時間非常短且線程不希望在操作系統調度中頻繁上下文切換的場景。這通常用在低延遲系統中,或者當線程數量不多于CPU核心數量時,確保CPU不會在等待鎖時空閑。

示例如下:

#include <atomic>  
#include <iostream>  
#include <thread>  
#include <chrono>  class Spinlock {  
private:  std::atomic_flag lock_ = ATOMIC_FLAG_INIT;  public:  void lock() {  while (lock_.test_and_set(std::memory_order_acquire)) {  // 循環直到鎖被釋放  }  }  void unlock() {  lock_.clear(std::memory_order_release);  }  bool try_lock() {  return !lock_.test_and_set(std::memory_order_acquire);  }  
};  void threadFunction(Spinlock& lock, int id) {  lock.lock();  std::cout << "Thread " << id << " entered critical section\n";  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模擬耗時操作  std::cout << "Thread " << id << " leaving critical section\n";  lock.unlock();  
}  int main() {  Spinlock lock;  std::thread t1(threadFunction, std::ref(lock), 1);  std::thread t2(threadFunction, std::ref(lock), 2);  t1.join();  t2.join();  return 0;  
}

????????在這個例子中,Spinlock?類使用了一個?std::atomic_flag?類型的成員變量?lock_?來實現鎖的功能。lock_?的?test_and_set?方法會嘗試將標志設置為?true?并返回之前的值。如果返回?false,表示鎖之前未被鎖定,當前線程成功獲取鎖;如果返回?true,表示鎖已被其他線程持有,當前線程需要繼續循環等待。

????????請注意,自旋鎖在多核處理器上且等待時間較短時通常表現良好,但在等待時間較長或鎖競爭激烈時可能會導致性能問題。因此,在選擇使用自旋鎖時,需要根據具體的應用場景和性能要求做出合理的選擇。

8.總結

????????C++標準庫提供了多種類型的互斥鎖,每種鎖都有其特定的用途和特點。選擇合適的互斥鎖類型可以有效提高程序的并發性能和安全性。

C++慣用法之RAII思想: 資源管理_raii 思想-CSDN博客

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

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

相關文章

【python腳本】批量檢測sql延時注入

文章目錄 前言批量檢測sql延時注入工作原理腳本演示 前言 SQL延時注入是一種在Web應用程序中利用SQL注入漏洞的技術&#xff0c;當傳統的基于錯誤信息或數據回顯的注入方法不可行時&#xff0c;例如當Web應用進行了安全配置&#xff0c;不顯示任何錯誤信息或敏感數據時&#x…

【TS】TypeScript 原始數據類型深度解析

&#x1f308;個人主頁: 鑫寶Code &#x1f525;熱門專欄: 閑話雜談&#xff5c; 炫酷HTML | JavaScript基礎 ?&#x1f4ab;個人格言: "如無必要&#xff0c;勿增實體" 文章目錄 TypeScript 原始數據類型深度解析一、引言二、基礎原始數據類型2.1 boolean2.2 …

蒼穹外賣--sky-take-out(四)10-12

蒼穹外賣--sky-take-out&#xff08;一&#xff09; 蒼穹外賣--sky-take-out&#xff08;一&#xff09;-CSDN博客?編輯https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2…

Unity動畫系統(2)

6.1 動畫系統基礎2-3_嗶哩嗶哩_bilibili p316 模型添加Animator組件 動畫控制器 AnimatorController AnimatorController 可以通過代碼控制動畫速度 建立動畫間的聯系 bool值的設定 trigger p318 trigger點擊的時候觸發&#xff0c;如喊叫&#xff0c;開槍及換子彈等&#x…

在js中如何Json字符串格式不對,如何處理

如果 JSON 字符串格式不正確&#xff0c;解析它時會拋出異常&#xff0c;但我們可以嘗試盡可能提取有效的信息。以下是一個方法&#xff0c;可以使用正則表達式和字符串操作來提取部分有效的 JSON 內容&#xff0c;即使整個字符串無法被 JSON.parse 完全解析。 示例代碼如下&a…

錯誤 [WinError 10013] 以一種訪問權限不允許的方式做了一個訪問套接字的嘗試 python ping

報錯提示&#xff1a;錯誤 [WinError 10013] 以一種訪問權限不允許的方式做了一個訪問套接字的嘗試 用python做了一個批量ping腳本&#xff0c;在windows專業版上沒問題&#xff0c;但是到了windows服務器就出現這個報錯 解決方法&#xff1a;右鍵 管理員身份運行 這個腳本 …

sql拉鏈表

1、定義&#xff1a;維護歷史狀態以及最新數據的一種表 2、使用場景 1、有一些表的數據量很大&#xff0c;比如一張用戶表&#xff0c;大約1億條記錄&#xff0c;50個字段&#xff0c;這種表 2.表中的部分字段會被update更新操作&#xff0c;如用戶聯系方式&#xff0c;產品的…

compute和computeIfAbsent的區別和用法

compute和computeIfAbsent都是Map接口中的默認方法&#xff0c;用于在映射中進行鍵值對的計算和更新。它們的主要區別在于它們的行為和使用場景。 compute 方法 定義: V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);參數: k…

在 WebGPU 與 Vulkan 之間做出正確的選擇(Making the Right Choice between WebGPU vs Vulkan)

在 WebGPU 與 Vulkan 之間做出正確的選擇&#xff08;Making the Right Choice between WebGPU vs Vulkan&#xff09; WebGPU 和 Vulkan 之間的主要區別WebGPU 是什么&#xff1f;它適合誰使用&#xff1f;Vulkan 是什么&#xff1f;它適合誰使用&#xff1f;WebGPU 和 Vulkan…

修改CentOS7 yum源

修改CentOS默認yum源為阿里鏡像源 備份系統自帶yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 下載ailiyun的yum源配置文件 CentOS7 yum源如下&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun…

AI領域最需要掌握的技術是什么?

在AI領域&#xff0c;掌握一系列核心技術和相關知識是非常重要的&#xff0c;以下是AI專業人士最需要掌握的一些關鍵技術&#xff1a; 1. **數學基礎** - 線性代數&#xff1a;用于處理向量和矩陣&#xff0c;是機器學習和深度學習的基石。 - 微積分&#xff1a;用于理解函數的…

SpringBoot項目使用WebSocket提示Error creating bean with name ‘serverEndpointExporter‘

問題描述&#xff1a;WebSocket在Controller中正常工作&#xff0c;但是在之后使用SpringBootTest進行單元測試的時候&#xff0c;突然提示WebSocket的相關錯誤。 錯誤提示&#xff1a; Exception encountered during context initialization - cancelling refresh attempt: …

項目中的代碼記錄日常

項目中的代碼記錄日常 /// <summary> /// 修改任務狀態 /// </summary> private void StartProcess21() {Process21Task new Thread(() >{while (CommonUtility.IsWorking){try{if (tPAgvTasksList.Count > 0){Parallel.ForEach(tPAgvTasksList, new Paral…

gitlab push的時候需要密碼,你忘記了密碼

情景: 忘記密碼,且登入網頁端gitlab的密碼并不能在push的時候使用,應該兩者是兩個不同的密碼 解決方法: 直接設置ssh密鑰登入,不使用密碼gitlab添加SSH密鑰——查看本地密鑰 & 生成ssh密鑰_gitlab生成ssh密鑰-CSDN博客

[OC]蘿卜圈Python手動機器人腳本

這是給機器人設置的端口&#xff0c;對照用 代碼 # #作者:溥哥’ ##機器人驅動主程序 #請在main中編寫您自己的機器人驅動代碼 import msvcrt def main():a"none"while True:key_input msvcrt.getch()akey_inputif abw:print(a)robot_drv.set_motors(1,40,2,40,3,…

uniapp學習筆記

uniapp官網地址&#xff1a;https://uniapp.dcloud.net.cn/ 學習源碼&#xff1a;https://gitee.com/qingnian8/uniapp-ling_project.git 顏色網址&#xff1a;https://colordrop.io/ uniapp中如何獲取導航中的路由信息&#xff1f; onLoad(e){console.log(e)console.log(e.w…

C#根據類的public屬性加載類中對應字段的XML

<?xml version"1.0" encoding"utf-8"?> <root><UserLogin ID"0" UserName"" UserPassWord"" Level"1" PowerName"默認用戶" PowerID"0" Remark"" IsEnabled"…

java八股文面試題

Java八股文面試題通常涵蓋了Java語言的基礎知識、高級特性、框架應用、數據庫操作等多個方面。以下是一些常見的Java面試題及其詳細回答&#xff0c;按照不同的主題進行分類&#xff1a; 一、Java基礎 面向對象的特征有哪些&#xff1f; 抽象&#xff1a;忽略與當前目標無關的…

2.2.4 C#中顯示控件BDPictureBox 的實現----ROI交互

2.2.4 C#中顯示控件BDPictureBox 的實現----ROI交互 1 界面效果 在設定模式下&#xff0c;可以進行ROI 框的拖動&#xff0c;這里以Rect1舉例說明 2 增加ROI類定義 /// <summary> /// ROI_single /// 用于描述圖片感興趣區域 /// type: 0:Rect1;1:Rect2;2:Circle ;3:…

C++ //練習 14.31 我們的StrBlobPtr類沒有定義拷貝構造函數、賦值運算符及析構函數,為什么?

C Primer&#xff08;第5版&#xff09; 練習 14.31 練習 14.31 我們的StrBlobPtr類沒有定義拷貝構造函數、賦值運算符及析構函數&#xff0c;為什么&#xff1f; 環境&#xff1a;Linux Ubuntu&#xff08;云服務器&#xff09; 工具&#xff1a;vim 解釋&#xff1a; 因為…