CPP多線程2:多線程競爭與死鎖問題

在多線程編程中,多個線程協同工作能顯著提升程序效率,但當它們需要共享和操作同一資源時,潛在的問題也隨之而來;線程間的執行順序不確定性可能導致資源競爭,可能引發死鎖,讓程序陷入停滯。

多線程競爭問題示例

我們現在已經知道如何在c++11中創建線程,那么如果多個線程需要操作同一個變量呢?

#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void count10000() {for (int i = 1; i <= 10000; i++)n++;
}
int main() {thread th[100];for (thread &x : th)x = thread(count10000);for (thread &x : th)x.join();cout << n << endl;return 0;
}

可能的兩次輸出分別是:

991164
996417

我們的輸出結果應該是1000000,可是為什么實際輸出結果比1000000小呢?

在多線程的執行順序——同時進行、無次序,所以這樣就會導致一個問題:多個線程進行時,如果它們同時操作同一個變量,那么肯定會出錯。為了應對這種情況,c++11中出現了std::atomic和std::mutex。

std::mutex

std::mutex是 C++11 中最基本的互斥量,一個線程將mutex鎖住時,其它的線程就不能操作mutex,直到這個線程將mutex解鎖。根據這個特性,我們可以修改一下上一個例子中的代碼:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx;
void count10000(){for(auto i=0;i<10000;i++){mtx.lock();n++;mtx.unlock();}
}
int main(){thread th[100];for (thread &x : th)x = thread(count10000);for (thread &x : th)x.join();cout<<n<<endl;return 0;
}

mutex的常用成員函數
在這里插入圖片描述

std::lock_gard

使用mutex需要上鎖解鎖,但有時由于程序員忘記或者其他奇怪問題時,lock_gard可以自動解鎖。其原理大概是構造時自動上鎖,析構時自動解鎖。示例如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){th[i]=thread(count100000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}

std::unique_lock

std::unique_lock是 C++ 標準庫中提供的一個互斥量封裝類,用于在多線程程序中對互斥量進行加鎖和解鎖操作。它的主要特點是可以對互斥量進行更加靈活的管理,包括延遲加鎖、條件變量、超時等。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>using namespace std;int n = 0;
mutex mtx;void count100000() {for (auto i = 0; i < 100; ++i) {unique_lock<mutex> lock1(mtx);n++;lock1.unlock();  // 提前解鎖,體現unique_lock的靈活性this_thread::sleep_for(chrono::milliseconds(1));  // 模擬耗時操作lock1.lock();n--;}
}int main() {thread th[10];for (int i = 0; i < 10; ++i) {th[i] = thread(count100000);}for (int i = 0; i < 10; ++i) {th[i].join();  // 等待所有線程完成}cout << n << endl;  // 所有線程結束后輸出結果(理論上應為0)return 0;
}

公共構造函數

函數作用
unique_lock() noexcept = default默認構造函數,創建一個未關聯任何互斥量的std::unique_lock對象。
explicit unique_lock(mutex_type& m)構造函數,使用給定的互斥量m進行初始化,并對該互斥量進行加鎖操作。
unique_lock(mutex_type& m, defer_lock_t) noexcept構造函數,使用給定的互斥量m進行初始化,但不對該互斥量進行加鎖操作。
unique_lock(mutex_type& m, try_to_lock_t) noexcept構造函數,使用給定的互斥量m進行初始化,并嘗試對該互斥量進行加鎖操作。如果加鎖失敗,則創建的std::unique_lock對象不與任何互斥量關聯。
unique_lock(mutex_type& m, adopt_lock_t) noexcept構造函數,使用給定的互斥量m進行初始化,并假設該互斥量已經被當前線程成功加鎖。
std::unique_lock使用非常靈活方便,上述操作的使用方式將在課程視頻中作詳細介紹。

常用成員函數

函數作用
lock()嘗試對互斥量進行加鎖操作,如果當前互斥量已經被其他線程持有,則當前線程會被阻塞,直到互斥量被成功加鎖。
try_lock()嘗試對互斥量進行加鎖操作,如果當前互斥量已經被其他線程持有,則函數立即返回false,否則返回true。
try_lock_for(const std::chrono::duration<Rep, Period>& rel_time)嘗試對互斥量進行加鎖操作,如果當前互斥量已經被其他線程持有,則當前線程會被阻塞,直到互斥量被成功加鎖,或者超過了指定的時間。
try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time)嘗試對互斥量進行加鎖操作,如果當前互斥量已經被其他線程持有,則當前線程會被阻塞,直到互斥量被成功加鎖,或者超過了指定的時間點。
unlock()對互斥量進行解鎖操作。

std::atomic

mutex很好地解決了多線程資源爭搶的問題,但它也有缺點:太……慢……了……

比如前面我們定義了100個thread,每個thread要循環10000次,每次循環都要加鎖、解鎖,這樣固然會浪費很多的時間,那么該怎么辦呢?接下來就是atomic大展拳腳的時間了。

#include <iostream>
#include <atomic>
#include <thread>
using namespace std;atomic<int> n{0};// 列表初始化void count10000(){for(int i=0;i<10000;i++)n++;
}int main(){thread th[10];for(thread& x:th)x=thread(count10000);for(auto& x:th)x.join();cout<<n<<endl;return 0;
}

可以看到,我們只是改動了n的類型(int->std::atomic_int),其他的地方一點沒動,輸出卻正常了。

atomic,本意為原子,可解釋為:

原子操作是最小的且不可并行化的操作。

atomic常用成員函數
在這里插入圖片描述

死鎖問題

在多個線程中由于上鎖順序問題可能導致線程卡死,如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx1;
mutex mtx2;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
void count200000(){for(auto i=0;i<200000;i++){lock_guard<mutex> lock2(mtx2);lock_guard<mutex> lock1(mtx1);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){if(i%2==0)th[i]=thread(count100000);elseth[i]=thread(count200000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}

這是因為在一個線程count100000中mtx1上鎖后,另一個線程count200000也正好將mtx2上鎖,于是這兩個線程沒辦法獲得另一個mutex,這就是死鎖問題。

解決方法就是保持一樣的上鎖順序,于是當一個線程A搶到第一個mutex時,其他線程無法再獲得mutex,即只能線程A按著順序處理完所有事物。示例如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;int n=0;
mutex mtx1;
mutex mtx2;
void count100000(){for(auto i=0;i<100000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
void count200000(){for(auto i=0;i<200000;i++){lock_guard<mutex> lock1(mtx1);lock_guard<mutex> lock2(mtx2);n++;n--;}
}
int main(){thread th[10];for(int i=0;i<10;i++){if(i%2==0)th[i]=thread(count100000);elseth[i]=thread(count200000);}for(int i=0;i<10;i++){th[i].join();}cout<<n<<endl;return 0;
}

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

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

相關文章

全國產飛騰d2000+復旦微690t信號處理模塊

UD VPX-404是基于高速模擬/數字采集回放、FPGA信號實時處理、CPU主控、高速SSD實時存儲架構開發的一款高度集成的信號處理組合模塊&#xff0c;采用6U VPX架構&#xff0c;模塊裝上外殼即為獨立整機&#xff0c;方便用戶二次開發。 UD VPX-404模塊的國產率可達到100%&#xff0…

物聯網 (IoT) 的頂級硬件平臺

物聯網 &#xff08;IoT&#xff09; 的頂級硬件平臺IoT&#xff08;物聯網&#xff09;不再是一個流行詞。隨著每天出現幾個鼓舞人心的用例&#xff0c;多家公司現在正在探索如何利用該技術實現業務增長。無論實施何種其他技術&#xff0c;基于物聯網的新設備正迅速成為一項重…

TCP傳輸層協議(4)

TCP應用層協議&#xff08;4&#xff09; 流量控制 接收端處理數據的速度是有限的. 如果發送端發的太快, 導致接收端的緩沖區被打滿, 這個時候如果發送端繼續發送, 就會造成丟包, 繼而引起丟包重傳等等一系列連鎖反應. 因此 TCP 支持根據接收端的處理能力, 來決定發送端的發送速…

雙向SSL認證之Apache實戰配置

防御未授權訪問&#xff0c;為企業級應用構筑雙重身份驗證防線 本文是關于Apache配置雙向SSL認證的深度技術指南&#xff0c;包含全流程操作、調試技巧及企業級解決方案&#xff0c;適用于運維工程師和安全管理員。 1.為什么需要雙向認證 &#xff1f; 核心價值 &#x1f51…

JavaScript 實用工具方法小全

1. 精確獲取小數位數/*** 獲取數字的小數位數&#xff08;支持科學計數法&#xff09;* param {number|string} num - 要檢查的數字&#xff0c;可以是數字或字符串形式* returns {number} 返回小數部分的位數* * 實現原理&#xff1a;* 1. 處理科學計數法&#xff08;如1.23e-…

【易錯題】C語言

今日遇到的易錯題 #include <stdio.h> int i;//全局變量默認初始化是0 int main() {i--;//-1if (i > sizeof(i)){printf(">\n");}else{printf("<\n");}return 0; }易錯點&#xff1a;sizeof的返回值類型實際為無符號整形&#xff0c;因此編…

第七十五章:AI的“思維操控師”:Prompt變動對潛在空間(Latent Space)的影響可視化——看懂AI的“微言大義”!

Prompt變動對潛在空間影響前言&#xff1a;AI的“思維操控師”——Prompt變動對潛在空間的影響可視化&#xff01;第一章&#xff1a;痛點直擊——Prompt“難伺候”&#xff1f;改一個字就“面目全非”&#xff01;第二章&#xff1a;AI的“思維圣地”&#xff1a;潛在空間&…

【計算機視覺與深度學習實戰】03基于Canny、Sobel和Laplacian算子的邊緣檢測系統設計與實現

第一章 引言 邊緣檢測作為計算機視覺和圖像處理領域的核心技術之一,在現代數字圖像分析中占據著舉足輕重的地位。邊緣是圖像中亮度變化劇烈的區域,通常對應著物體的輪廓、表面方向的不連續性、材質變化或照明條件的改變。準確而高效的邊緣檢測不僅是圖像分割、特征提取、模式…

【大語言模型 02】多頭注意力深度剖析:為什么需要多個頭

多頭注意力深度剖析&#xff1a;為什么需要多個頭 - 解密Transformer的核心升級 關鍵詞&#xff1a;多頭注意力、Multi-Head Attention、注意力頭、并行計算、特征學習、Transformer架構、深度學習 摘要&#xff1a;在掌握了Self-Attention基礎后&#xff0c;本文深入探討多頭注…

Python Condition對象wait方法使用與修復

在 Python 中&#xff0c;Condition 對象用于線程同步&#xff0c;其 wait() 方法用于釋放鎖并阻塞線程&#xff0c;直到被其他線程喚醒。使用不當可能導致死鎖、虛假喚醒或邏輯錯誤。以下是常見問題及修復方案&#xff1a;常見問題與修復方案1. 未檢查條件&#xff08;虛假喚醒…

嵌入式硬件——ARM

一、ARM體系結構程序編譯的過程&#xff1a;預處理&#xff08;.c-.i&#xff09;&#xff1a;宏替換&#xff0c;頭文件展開&#xff0c;去掉注釋&#xff0c;特殊符號的處理編譯&#xff08;.i-.s&#xff09;&#xff1a;C語言轉換成匯編語言匯編&#xff08;.s-.o&#xff…

Flutter 以模塊化方案 適配 HarmonyOS 的實現方法

Flutter 以模塊化方案 適配 HarmonyOS 的實現方法 Flutter的SDK&#xff1a; https://gitcode.com/openharmony-tpc/flutter_flutter 分支Tag&#xff1a;3.27.5-ohos-0.1.0-beta DevecoStudio&#xff1a;DevEco Studio 5.1.1 Release HarmonyOS版本&#xff1a;API18 本文使…

Redis入門與背景詳解:構建高并發、高可用系統的關鍵基石

本文前言認識Redis單機架構淺談分布式系統分布式是什么數據庫分離和負載均衡引入緩存數據庫分庫分表引入微服務念補充小結Redis特性介紹持久化支持集群高可用快Redis的應用場景總結前言 在當今這個數據驅動的時代&#xff0c;應用的性能和可擴展性已成為衡量其成功的關鍵指標。…

Mysql常見的優化方法

數據庫優化(底層基礎優化) 數據庫層面的優化是性能“基礎"&#xff0c; 主要包含架構設計、存儲引擎、表結構、索引策略、配置參數等方面考慮。目標是減少資源(CPU、IO和內存)消耗。 架構設計 讀寫分離&#xff1a;將"讀操作"和"寫操作"分離到不同的數…

利用Claude Code打造多語言網站內容翻譯工具:出海應用開發全流程實戰教程

一、工具選型與準備Claude Code 簡介 Claude Code 是 Anthropic 公司推出的 AI 編程助手&#xff0c;可以輔助開發者生成代碼、優化代碼結構、進行代碼解釋等&#xff0c;支持多種主流編程語言。開發環境準備 Claude Code 賬號或 API 接入權限Node.js 或 Python 環境&#xff0…

集成運算放大器(反向比例,同相比例)

基礎知識&#xff1a;反相比例運算原理&#xff1a;示波器顯示&#xff1a;結論&#xff1a;放大倍數為-R2/R1。R3的大小約等于R1與R2的并聯電阻。由于放大器的最大輸出電壓取決于供電電壓&#xff0c;所以如果R2為7k時&#xff0c;會導致失真。同向比例原理&#xff1a;示波器…

【HBase】HBaseJMX 接口監控信息實現釘釘告警

目錄 一、JMX 簡介 二、JMX監控信息釘釘告警實現 一、JMX 簡介 官網&#xff1a;Apache HBase ? Reference Guide JMX &#xff08;Java管理擴展&#xff09;提供了內置的工具&#xff0c;使您能夠監視和管理Java VM。要啟用遠程系統的監視和管理&#xff0c;需要在啟動Java…

SQL 語言規范與基礎操作指南

SQL 語言規范與基礎操作指南 SQL 作為數據庫操作的核心語言&#xff0c;遵循規范的語法和書寫習慣不僅能提高代碼可讀性&#xff0c;還能減少錯誤。本文整理了 SQL 的基礎規則、書寫規范及常用操作&#xff0c;適合初學者快速上手。 一、SQL 基本規則 1. 書寫格式 SQL 語句可寫…

產業園IBMS智能化集成系統功能有哪些?

產業園 IBMS&#xff08;建筑集成管理系統&#xff09;智能化集成系統是針對產業園 “多業態、多系統、多租戶” 特點設計的全局管理平臺&#xff0c;通過整合樓宇自控、安防、消防、能源、停車、租戶服務等子系統&#xff0c;實現 “集中監控、協同聯動、數據驅動、靈活服務”…

線性代數之兩個宇宙文明關于距離的對話

矢量的客觀性和主觀性宇宙中飄過來一個自由矢量&#xff0c;全世界的人都可以看到&#xff0c;大家都在想&#xff0c;怎么描述它呢&#xff0c;總不能指著它說“那個矢量”吧。數學家很聰明&#xff0c;于是建立了一個坐標系&#xff0c;這個矢量投影到坐標系下&#xff0c;就…