c++ 讀寫鎖的理解

1.概要

讀寫鎖的理解
讀的時候,只要是讀的線程都不受限制,但不能寫。
寫的時候,線程獨占,任何寫和讀的線程都不可以。

最初我以為,只有限制寫就可以了,讀完全不受現在,但是有可能讀到不完整的數據,比如寫一半的數據等等待,所以這就是對讀的那部分控制,共享鎖的價值。

2.std::shared_mutex 詳細說明

std::shared_mutex是C++17中引入的一個同步原語,用于在多線程環境下提升對共享資源的訪問效率。與傳統的互斥鎖(如std::mutex)不同,std::shared_mutex允許多個線程以只讀模式共享對資源的訪問,但寫入操作必須獨占資源,以防止同時有其他線程對共享資源進行讀取或寫入。

關鍵特性

  1. 兩種訪問級別
    • 共享訪問(讀鎖):多個線程可以同時擁有讀鎖,允許它們并行讀取數據。
    • 獨占訪問(寫鎖):只有一個線程可以擁有寫鎖,以此來修改數據。當一個線程擁有寫鎖時,其他線程無法獲得讀鎖或寫鎖。
  2. 適用場景:這種機制非常適合于多讀少寫的場景,因為它能夠最大化讀操作的并發性,同時確保寫操作的安全性。

使用方法

為了安全地使用std::shared_mutex,你需要理解其兩種鎖模型:

  1. 獨占鎖(寫鎖):當你需要對共享數據進行寫入操作時,應該使用獨占鎖。獨占鎖確保了在執行寫入操作期間,沒有其它線程對數據進行讀或寫操作。
  2. 共享鎖(讀鎖):當多個線程需要讀取共享數據但不進行修改時,可以使用共享鎖。多個線程可以同時擁有讀鎖,但如果有線程正在使用獨占鎖(寫鎖),則其他線程無法獲得讀鎖或寫鎖。

示例

下面是一個簡單的示例,展示了如何使用std::shared_mutex來同步對共享資源的訪問:

#include <iostream>  
#include <thread>  
#include <vector>  
#include <shared_mutex>  std::shared_mutex mtx;  
int shared_data = 0;  void reader(int id) {  std::shared_lock<std::shared_mutex> lock(mtx);  // 讀取共享數據  std::cout << "Reader " << id << " reads: " << shared_data << std::endl;  
}  void writer(int value) {  std::unique_lock<std::shared_mutex> lock(mtx);  // 修改共享數據  shared_data = value;  std::cout << "Writer writes: " << shared_data << std::endl;  
}  int main() {  std::vector<std::thread> threads;  // 創建多個讀線程  for (int i = 0; i < 5; ++i) {  threads.emplace_back(reader, i);  }  // 創建一個寫線程,稍后進行寫入操作  threads.emplace_back(writer, 42);  // 等待所有線程完成  for (auto& t : threads) {  t.join();  }  return 0;  
}

請注意,上述示例中的寫線程在創建后并不會立即執行,因為線程的調度順序是由操作系統決定的。為了更精確地控制讀寫操作的順序,你可能需要使用條件變量或其他同步機制。

3.共享鎖和沒有鎖有啥區別

C++中的共享鎖(如std::shared_lock配合std::shared_mutex使用)和沒有鎖(即不使用任何同步機制)之間存在顯著的區別。這些區別主要體現在對多線程環境中共享資源訪問的控制和安全性上。

沒有鎖

如果不使用任何鎖或同步機制,多個線程可以自由訪問和修改共享資源,這可能導致以下問題:

  1. 數據競爭(Data Race):當兩個或更多的線程并發訪問同一內存位置,且至少有一個線程是寫入操作,并且線程間沒有使用適當的同步來協調這些訪問時,就會發生數據競爭。數據競爭會導致未定義的行為,因為寫入操作的順序是不確定的。
  2. 不一致的狀態:由于線程之間沒有協調它們的訪問,一個線程可能看到由另一個線程部分修改的變量,導致程序處于不一致的狀態。
  3. 難以調試:沒有鎖的多線程程序通常很難調試,因為線程的執行順序是不確定的,并且程序的行為可能因環境而異(例如,CPU核心數量、線程調度策略等)。

共享鎖

共享鎖允許多個線程同時讀取共享資源,但阻止任何線程在已有線程讀取或寫入時寫入資源。使用共享鎖可以解決上述問題:

  1. 防止數據競爭:通過確保在讀取或寫入共享資源時只有一個線程可以執行,共享鎖防止了數據競爭。
  2. 保持數據一致性:由于寫入操作是獨占的,因此當一個線程正在寫入時,其他線程無法讀取或寫入,從而確保了數據的一致性。
  3. 提高并發性:與互斥鎖(如std::mutex)相比,共享鎖允許多個線程同時讀取共享資源,從而提高了并發性。這對于多讀少寫的場景特別有用。
  4. 簡化編程:使用共享鎖可以簡化多線程編程,因為程序員不需要手動協調線程之間的訪問,而是可以依靠鎖機制來自動處理。

總結

沒有鎖的多線程程序可能導致數據競爭、不一致的狀態和難以調試的問題。而共享鎖通過確保在讀取或寫入共享資源時只有一個線程可以執行,解決了這些問題,并提高了并發性。因此,在需要多線程訪問共享資源的場景中,使用適當的鎖或同步機制是非常重要的。

4.讀寫鎖和普通鎖的對比試驗

為了對比C++中的讀寫鎖(std::shared_mutex?或其他第三方讀寫鎖實現)和普通鎖(如?std::mutex)的性能,我們可以設計一個簡單的試驗。在這個試驗中,我們將創建多個線程,其中一些線程作為讀者(只讀取數據),而另一些線程作為寫者(修改數據)。

以下是一個簡化的示例,展示了如何使用std::mutexstd::shared_mutex來模擬這種場景,并進行性能對比。

使用?std::mutex

#include <iostream>  
#include <thread>  
#include <vector>  
#include <mutex>  
#include <chrono>  std::mutex mtx;  
int data = 0;  void reader(int id) {  for (int i = 0; i < 10000; ++i) {  std::lock_guard<std::mutex> lock(mtx);  // 假設我們只是讀取數據  int value = data;  // ...(省略其他讀取操作)  }  
}  void writer(int id) {  for (int i = 0; i < 1000; ++i) {  std::lock_guard<std::mutex> lock(mtx);  // 假設我們只是寫入數據  data = id;  // ...(省略其他寫入操作)  std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 模擬耗時寫入  }  
}  int main() {  auto start = std::chrono::high_resolution_clock::now();  std::vector<std::thread> threads;  for (int i = 0; i < 5; ++i) {  threads.emplace_back(reader, i);  }  for (int i = 0; i < 2; ++i) {  threads.emplace_back(writer, i);  }  for (auto& t : threads) {  t.join();  }  auto end = std::chrono::high_resolution_clock::now();  std::chrono::duration<double> diff = end - start;  std::cout << "Time taken with std::mutex: " << diff.count() << " s\n";  return 0;  
}

使用?std::shared_mutex

 
#include <iostream>  
#include <thread>  
#include <vector>  
#include <shared_mutex>  
#include <chrono>  std::shared_mutex smtx;  
int data = 0;  void reader(int id) {  for (int i = 0; i < 10000; ++i) {  std::shared_lock<std::shared_mutex> lock(smtx);  // 假設我們只是讀取數據  int value = data;  // ...(省略其他讀取操作)  }  
}  void writer(int id) {  for (int i = 0; i < 1000; ++i) {  std::unique_lock<std::shared_mutex> lock(smtx, std::defer_lock);  lock.lock(); // 使用unique_lock來鎖定,因為shared_lock不允許升級鎖  // 假設我們只是寫入數據  data = id;  // ...(省略其他寫入操作)  std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 模擬耗時寫入  }  
}  // main函數與上述類似,只是鎖的類型改變了

注意:在上面的std::shared_mutex示例中,我們使用std::unique_lock來鎖定寫操作,因為std::shared_lock不允許鎖升級(即,從共享鎖升級到獨占鎖)。但是,為了保持示例的簡單性,我們仍然使用std::shared_mutex來展示讀寫鎖的基本概念。

性能對比

要獲得準確的性能對比,您應該多次運行這兩個程序,并可能使用更復雜的基準測試工具來測量CPU時間、線程爭用等。但是,從概念上講,當讀者數量遠多于寫者時,使用std::shared_mutex(或類似的讀寫鎖)應該比使用std::mutex具有更好的性能,因為多個讀者可以同時訪問數據,而不會被彼此阻塞。

5.defer_lock 啥意思

在C++中,std::defer_lock是一個標簽類型,它用于與互斥體(std::mutexstd::timed_mutexstd::recursive_mutexstd::recursive_timed_mutexstd::shared_mutex等)的std::unique_lockstd::shared_lock結合使用,以指示在構造時不立即鎖定互斥體。

具體來說,當你使用std::unique_lockstd::shared_lock時,默認情況下,互斥體會在std::unique_lockstd::shared_lock的構造函數中被鎖定。但是,如果你使用std::defer_lock標簽,則互斥體不會在構造函數中立即鎖定,你可以稍后通過調用lock()try_lock()unlock()等方法來控制鎖定和解鎖。

這在某些情況下可能是有用的,例如:

  1. 條件變量:當你使用條件變量(如std::condition_variable)時,你可能希望在等待條件變量之前不鎖定互斥體,因為條件變量的wait()方法會自動解鎖互斥體并在條件滿足時重新鎖定它。在這種情況下,你可以使用std::defer_lock來構造一個未鎖定的std::unique_lock,然后調用wait()
  2. 復雜的同步邏輯:在更復雜的同步場景中,你可能希望根據某些條件來決定是否立即鎖定互斥體。使用std::defer_lock可以讓你延遲這個決策。

下面是一個使用std::defer_lock的示例:

#include <iostream>  
#include <mutex>  
#include <thread>  
#include <chrono>  std::mutex mtx;  void print_block(int n, char c) {  std::unique_lock<std::mutex> lck(mtx, std::defer_lock);  // 在這里,mtx還沒有被鎖定  // 模擬一些工作  std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 現在我們決定鎖定互斥體  lck.lock();  for (int i = 0; i < n; ++i) {  std::cout << c;  }  std::cout << '\n';  // 解鎖(但在這個例子中,當lck離開作用域時會自動解鎖)  lck.unlock();  // 或者簡單地讓lck離開作用域,它會在析構時自動解鎖  
}  int main() {  std::thread th1(print_block, 50, '*');  std::thread th2(print_block, 50, '$');  th1.join();  th2.join();  return 0;  
}

注意,在這個示例中,盡管我們使用了std::defer_lock,但實際上我們還是在lck.lock()調用時立即鎖定了互斥體。但是,這個例子展示了如何使用std::defer_lock來延遲鎖定決策。在更復雜的場景中,你可能會根據某些條件來決定是否調用lock()

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

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

相關文章

【初始類和對象】(實例講解!超級詳細!)

【初始類和對象】 前言1. 面向對象的初步認知1.1什么是面向對象1.2 面向對象與面向過程 2. 類的定義和使用2.1 簡單認識類2.2 類的定義格式 3. 知識的代碼舉例講解3.1 創建類、實例化類3.2 構造方法初始化類、this 3. 總結 前言 由于類和對象是我們在學習過程中需要接受的概念…

AI賦能未來教育:中國教學科研新藍圖

設“人啊 前言 回顧過去&#xff0c;傳統的教育模式以知識灌輸和應試為主&#xff0c;雖培養出大量人才&#xff0c;但也存在著學生創新能力不足、實踐經驗缺乏等問題。隨著時代的進步和科技的發展&#xff0c;傳統教育模式已難以滿足當今社會對人才的需求。然而&#xff0c;當…

LoadIncrementalHFiles 流程和原理

目錄 1. HBase Bulk Load 簡介 2. 流程 3. 原理 4. 使用注意事項 5.補充說明之"什么是移動文件" 1. HBase Bulk Load 簡介 LoadIncrementalHFiles是用于HBase的Bulk Load工具&#xff0c;允許用戶高效地將大量數據直接加載到HBase表中&#xff0c;而不是使用傳…

中國現代十大杰出人物顏廷利:好的司機不如好的同機

找好‘同機’者, 要比找好‘司機’者, 原因就是, ‘司機’雖好, 但不是‘同路人’, 再多努力的攀附都是徒勞, 至于‘同機’者, 即便是對方在自己的眼里心中都一無是處, 只不過, 他/她才是您旅途之中, 真真正正、風雨同舟的人…(升命學說) 21世紀東方哲學家思想家、科學家、當代…

孩子學編程和不學編程的差距?

隨著信息技術的飛速發展&#xff0c;編程已經成為一項非常重要的技能&#xff0c;不僅僅是在計算機領域&#xff0c;而且在各個行業都有著廣泛的應用。因此&#xff0c;讓孩子學習編程已經成為很多家長的選擇。那么&#xff0c;孩子學習編程和不學習編程之間有哪些差距呢&#…

TODESK遠控快捷鍵在哪里

在當今高度數字化的世界中&#xff0c;遠程工作和協作已經成為日常生活和業務運營的重要組成部分。Todesk作為一款出色的遠程協作軟件&#xff0c;為用戶提供了諸多功能&#xff0c;以確保流暢、高效的遠程連接體驗。其中&#xff0c;快捷鍵功能極大地提升了用戶的操作便捷性。…

高速、簡單、安全的以太彩光,銳捷網絡發布極簡以太全光 3.X 方案

從 2021 年 3 月正式推出到現在&#xff0c;銳捷網絡極簡以太全光方案已經走進第四個年頭。IT 仍在不斷向前發展&#xff0c;數字化進程深入&#xff0c;數字化業務增多&#xff0c;更廣泛的終端設備接入企業級園區網絡&#xff0c;對園區網絡提出了更高的要求&#xff0c;例如…

GDB斷點執行的次數

需求背景&#xff1a;條件斷點可能執行多次&#xff0c;但是可能在最后一次執行引發了后續的問題&#xff0c;但是斷點位置并非問題現場&#xff0c;如何使得斷點在最后一次停下來&#xff1f; 方法&#xff1a; 1.首先設置條件斷點 (gdb) b (gdb) cond breakpoint_number…

Linux NFS共享目錄配置漏洞

Linux NFS共享目錄配置漏洞 一、實驗目的二、實驗原理三、復現準備四、漏洞復現4.1、復現前提4.2、正式復現 一、實驗目的 利用 NFS共享目錄配置漏洞讀取目標主機的 /etc/passwd 文件內容NFS 服務配置漏洞&#xff0c;賦予了根目錄遠程可寫權限&#xff0c;導致 /root/.ssh/au…

關系型數據庫VS非關系型數據庫

數據庫是存儲和組織數據的系統&#xff0c;主要分為兩大類&#xff1a; 關系型數據庫&#xff08;Relational Database Management Systems, RDBMS&#xff09; 非關系型數據庫&#xff08;NoSQL Databases&#xff09; 下面分別介紹這些類型及其區別&#xff1a; 關系型數…

im8mm 網絡卡死 Rx packets:1037578 errors:66 dropped:0 overruns:66 frame:0

1&#xff1a;網絡接收數據包異常 2&#xff1a;問題復現 問題在進行網絡數據包同吞吐量測試的時候出現的。同時發現&#xff0c;在使用iperf2測試時&#xff0c;是不會出現網絡中斷卡死的情況&#xff0c;使用 iperf3時才會出現此問題 指令(下面的指令運行在PC2上面&#xff…

AGV混合型電機驅動器|伺服控制器CNS-MI50H系列對電機的要求

混合型電機驅動器 CNS-MI50H系列涵蓋CNS-MI50HB-A、CNS-MI50HBN-A、CNS-MI50HDN-A、CNS-MI50HSN-A型號&#xff0c;專為 AGV 舵輪控制需求設計&#xff0c;集成舵輪轉向角度控制和驅動電機閉環控制。支持增量式編碼器&#xff0c;霍爾傳感器&#xff0c; 角度電位計&#xff0c…

自動化測試基礎 --- Jmeter

前置環境安裝 首先我們需要知道如何下載Jmeter 這里貼上下載網站Apache JMeter - Download Apache JMeter 我們直接解壓,然后在bin目錄下找到jemter.bat即可啟動使用 成功打開之后就是這個界面 每次打開可以用這種方式切換成簡體中文 或者直接修改properties文件修改對應的語言…

目標檢測算法YOLOv8簡介

YOLOv8論文尚未發布&#xff0c;YOLOv8由Ultralytics公司推出并維護&#xff0c;源碼見&#xff1a;https://github.com/ultralytics/ultralytics &#xff0c;于2024年1月發布v8.1.0版本&#xff0c;最新發布版本為v8.2.0&#xff0c;License為AGPL-3.0。 以下內容主要來自&am…

FFmpeg 中 -f 命令參數詳解

FFmpeg FFmpeg是一個開源的、功能強大的多媒體框架,它能夠處理幾乎所有格式的音頻和視頻文件。FFmpeg由Fabrice Bellard創立,并由Michael Niedermayer等人繼續開發。它包括了libavcodec(用于編解碼)、libavformat(用于格式轉換)、libavfilter(用于音視頻過濾)、libavd…

微信授權登錄01-PC端

目錄 ## 前言 1.準備工作 1.1 網站域名 1.2 微信開放平臺 2.授權授權登錄開發 2.1 前端開發 2.1.1 發起授權登錄跳轉至掃碼頁面 2.1.2 掃碼成功后回調處理 2.2 后端開發 2.2.1 根據code查詢用戶信息 2.2.2 自動注冊登錄 ## 后記 ## 前言 最近整了個AI助手網站&am…

React 學習-5

React 條件渲染: 與js中的寫法一致 注意&#xff1a;在 JavaScript 中&#xff0c;true && expression 總是返回 expression&#xff0c;而 false && expression 總是返回 false。 因此&#xff0c;如果條件是 true&#xff0c;&& 右側的元素就會被渲…

BL120協議Modbus RTU和Modbus TCP互轉

Modbus網關BL120是一款專注于Modbus協議之間相互轉換的通信設備。Modbus網關BL120支持多種下行采集協議&#xff0c;包括Modbus RTU和Modbus TCP&#xff0c;同時在上行轉發協議方面同樣支持Modbus RTU和Modbus TCP。Modbus網關為Modbus RTU和Modbus TCP協議的相互轉換提供了穩…

回爐重造java----單列集合(List,Set)

體系結構: 集合主要分為兩種&#xff0c;單列集合collection和雙列集合Map&#xff0c;區別在于單列集合一次插入一條數據&#xff0c;而雙列的一次插入類似于key-value的形式 單列集合collection 注:紅色的表示是接口&#xff0c;藍色的是實現類 ①操作功能: 增加: add()&am…

SRS流媒體服務器在Linux下的安裝

目錄 一、安裝 1、切換到管理員權限 2、先安裝基礎依賴環境 3、下載SRS源文件