跟我學C++中級篇——控制死鎖

一、同步和死鎖

在前面學習多線程和網絡編程時,都對線程中數據的同步和數據結構多線程訪問的安全問題進行了分析和說明。其實,多線程編程之所以難,難點之一就在這里,數據同步意味著效率和安全的平衡,而這里的安全有一個重要的關節就在于死鎖。
所以如果能很好的掌握線程間的同步以及防止出現死鎖這些問題后,基本對多線程的理解和控制就算是入了門。

二、死鎖場景

死鎖其實在大多數的開發者實際的開發中遇到的并不多。即使遇到也是在應用層面上用到,很少有主動寫出死鎖的。但這不代表死鎖少見,一般來說,常見的死鎖出現的場景有以下幾方面:
1、數據庫中的事務處理
比如多個事務都需要同時處理多張表,這里假在鎖定一些表后,有可能導致死鎖
2、多線程編程
這種情況下剛剛也提到了,較少有開發者會寫出這種死鎖的代碼,主要原因是,一般的開發場景也不會有多個資源在線程間同時訪問,即使有,很多開發者也會分開處理,等待通知后再進行
3、在辦公中訪問一些打印機等專享資源
這種情況其實比較常見,有時候兒打印機(包括文件等)啥的與OS或網絡的交互會犯傻,這時個兒往往重啟一下就好了
4、分布式應用中的同步請求
這種情況在分布式應用中比較常見,比如P2P中的互相通信如果操作不當,就有可能產生死鎖
5、大規模計算中的資源分配
比如在一些大規模計算中使用計算圖,就有可能在圖的處理中資源分配出現死鎖問題

三、避免死鎖

要想避免死鎖就必須了妥死鎖的條件,學習過操作系統相關知識的開發者都知道,死鎖的四個條件:
1、資源互斥
這個比較容易理解,資源必須是獨占的,即一個資源只能被一個線程占用。這就和食堂排隊打湯的一樣,大勺只有一個,只有一個人打好放勺子才可能另外一個人去使用
2、互相持有
也叫占有和等待,即一個線程完成一項工作需要兩個以上的資源,已經占有了一個,申請另外一個時,發現另外一個資源被其它線程占有,它會繼續申請而不釋放自己持有的資源。這個更好理解,就比如集一些類似火花、郵票或卡之類的東西(假設每個都是唯一的),如果三個人分別持有一套的三部分,大家都會尋找其它的而不會把自己的部分放棄
3、不可剝奪
即任何一個線程持有的資源無法被其它線程強制獲取。這個好理解,正常情況下,公民的財產不會被其它公民強行取得
4、循環等待
這個理解也不難,就是多個線程形成了一個環狀的資源持有,即A線程持有B線程需要的資源,B線程持有C線程的需要的資源…N線程持有A線程需要的資源。這就是書本上講的哲學家進餐的問題。
既然知道了死鎖造成的原因,那么解決死鎖就必須從上面的原因中找方法,只要打破任何一個條件,死鎖也就不存在了,即:
1、打破資源互斥,即將資源設計為允許多個線程訪問
2、打破互相持有,即所有的線程申請資源時一次性申請完成,要么成功,要么失敗,也或者可以在申請前把自己持有的資源釋放
3、打破不可剝奪,即允許在指定條件下獲取某些線程占有的資源
4、打破循環等待,即對資源進行有序控制不允許隨機申請,這樣就會斷開循環的鏈條

四、死鎖的解決方案

知道了避免死鎖的方法,就可以探討解決死鎖的編程方案了。一般來說,解決死鎖的方案有以下幾種:
1、無鎖編程
這種最容易理解了,打不過就躲過嘛。無鎖編程在前面分析說明了很多,此處不再展開
2、使用超時鎖
這個可以打破占有和等待。在C++11中提供了一些新的鎖如 std::timed_mutex 和 std::recursive_timed_mutex,看下面的例子:

#include <chrono>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
#include <vector>using namespace std::chrono_literals;std::mutex cout_mutex; // 控制到 std::cout 的訪問
std::timed_mutex mutex;void job(int id)
{std::ostringstream stream;for (int i = 0; i < 3; ++i){if (mutex.try_lock_for(100ms)){stream << "成功 ";std::this_thread::sleep_for(100ms);mutex.unlock();}elsestream << "失敗 ";std::this_thread::sleep_for(100ms);}std::lock_guard<std::mutex> lock{cout_mutex};std::cout << "[" << id << "] " << stream.str() << "\n";
}int main()
{std::vector<std::thread> threads;for (int i = 0; i < 4; ++i)threads.emplace_back(job, i);for (auto& i: threads)i.join();
}

3、可以將鎖排序
用來打破循環等待等條件,這種應用比較簡單,看下面的例子:

#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>std::mutex m1;
std::mutex m2;void taskFunc(bool order) {if (order) {// reversestd::lock_guard<std::mutex> lock2(m2);sleep(2);std::lock_guard<std::mutex> lock1(m1);std::cout << "lock risky!\n";} else {// orderstd::lock_guard<std::mutex> lock1(m1);sleep(2);std::lock_guard<std::mutex> lock2(m2);std::cout << "lock safe!\n";}
}int main() {std::thread t1([]() { taskFunc(true); });std::thread t2([]() { taskFunc(false); });t1.join();t2.join();return 0;
}

上面的代碼會產生死鎖,可以強制進行結束。
4、一次性獲取鎖
在C++17中提供了std::scoped_lock,相關的代碼可以查看前面的文章“跟我學C++中級篇——std::scoped_lock”
5、控制資源訪問
這種就方法就比較多了,比如在Windows平臺可以使用事件控制而在Linux平臺上使用條件變量進行,即沒有收到通知的一方不能去尋求資源的控制。典型的就是在Nginx的網絡資源獲取中就采用了這種控制的手段

如果在編程時沒有注意,在實際的應用中出現了意外,可以使用一些工具來檢查是否存在死鎖:
1、使用GDB
在編譯后,使用gdb:

gdb ./lockOrder
(gdb) r
Starting program: /home/fpc/qt65_project/lockOrder/lockOrder 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff4eff640 (LWP 843994)]
[New Thread 0x7ffff46fe640 (LWP 843995)]
[New Thread 0x7ffff3efd640 (LWP 843996)]
//此處如果不退出可以ctrl+C強制退出來
(gdb) info threadId   Target Id                                      Frame 
* 1    Thread 0x7ffff7317340 (LWP 843991) "lockOrder" __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=843995, futex_word=0x7ffff46fe910) at ./nptl/futex-internal.c:572    Thread 0x7ffff4eff640 (LWP 843994) "lockOrder" 0x00007ffff6ce57f8 in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7ffff4ebf150, rem=rem@entry=0x0) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:783    Thread 0x7ffff46fe640 (LWP 843995) "lockOrder" futex_wait (private=0, expected=2, futex_word=0x55555555c160 <m1>) at ../sysdeps/nptl/futex-internal.h:1464    Thread 0x7ffff3efd640 (LWP 843996) "lockOrder" futex_wait (private=0, expected=2, futex_word=0x55555555c1a0 <m2>) at ../sysdeps/nptl/futex-internal.h:146
(gdb) 

這樣就可以看到具體的死鎖的情況了
2、使用valgrind并啟用Helgrind工具
執行下列命令可得到相關日志:

valgrind --tool=helgrind --log-file=log.txt  ./lockOrder 

日志顯示(未貼全):

Thread #3: Exiting thread still holds 1 lock
==849869==    at 0x4B572C0: futex_wait (futex-internal.h:146)
==849869==    by 0x4B572C0: __lll_lock_wait (lowlevellock.c:49)
==849869==    by 0x4B5E001: lll_mutex_lock_optimized (pthread_mutex_lock.c:48)
==849869==    by 0x4B5E001: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:93)
==849869==    by 0x485051D: mutex_lock_WRK (hg_intercepts.c:935)
==849869==    by 0x4854CEE: pthread_mutex_lock (hg_intercepts.c:958)
==849869==    by 0x109DE1: __gthread_mutex_lock(pthread_mutex_t*) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109E65: std::mutex::lock() (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x10A09D: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109456: taskFunc(bool) (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
==849869==    by 0x109539: main::{lambda()#2}::operator()() const (in /home/fpc/qt65_project/lockOrder/build/lockOrder)
...

3、ThreadSanitizer (TSan)
它是集成于GCC和Clang的檢測工具,使用方法如下:

g++ -fsanitize=thread -g -o lockorder main.cpp
./lockorder

4、Lttng
這個沒有實驗成功,暫時沒找到原因。不過據說這個挺好用
5、其它
在不同的平臺上可能有不同的工具,比如在Windows平臺上的有名的工具Windbg和VS(ASan)等,安卓平臺上也有類似的工具;另外像前面介紹的Perf Tools工具,也可以間接的輔助進行線程死鎖的定位

五、總結

死鎖雖然在面試時反復被問到,但在實踐中真正寫出來或者遇到的并沒有想象的那么多。其實最主要的原因就是大多數的程序員都不會有這種開發的應用場景。但恰恰因為遇到的少,在實際中真正出現時,卻不知道從何下手。
還是老規矩,把基礎掌握好,會靈活的使用工具。只要發現定位了死鎖的問題,就可以根據產生死鎖的原因有針對的進行解決即可。沒有過不了的火焰山。

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

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

相關文章

【matlab】繪制maxENT模型的ROC曲線和omission curve

文章目錄 一、maxENT模型二、ROC曲線三、實操3.1 數據提取3.2 繪制ROC曲線3.3 繪制遺漏曲線3.4 多次訓練的ROC和測試的ROC 一、maxENT模型 前面的文章已經詳細講過了。 maxENT軟件運行后&#xff0c;會生成一個html報告&#xff0c;里面有ROC曲線&#xff0c;但我們往往需要自…

nginx 核心功能

目錄 一、基于授權的訪問控制 1. 使用 htpasswd 生成用戶認證文件 2. 修改 Nginx 主配置文件 二、基于客戶端的訪問控制 三、Nginx 虛擬主機 1. 基于域名的虛擬主機 2. 基于 IP 的虛擬主機 3. 基于端口的虛擬主機 四、LNMP 架構部署及應用 1. 安裝 MariaDB 2. 安裝并…

mongoose插入文檔,字段類型, 字段驗證, 刪除文檔,更新文檔,讀取文檔,查詢文檔的條件控制 ,字段篩選,數據排序,數據截取

、Mongoose 中與 文檔操作&#xff08;插入、查詢、更新、刪除&#xff09;及其相關功能&#xff08;字段類型、驗證、條件篩選、排序、分頁等&#xff09;相關示例&#xff1a; &#x1f4cb; 一、字段類型定義&#xff08;Schema Types&#xff09; const mongoose require…

類和對象 (拷貝構造函數和運算符重載)上

類和對象 (拷貝構造函數和運算符重載)上 拷貝構造函數存在的原因及解決的 C 語言問題 1. 淺拷貝帶來的問題 在 C 語言里&#xff0c;當對結構體或者數組進行拷貝操作時&#xff0c;執行的是淺拷貝。所謂淺拷貝&#xff0c;就是單純地把一個對象的所有成員變量的值復制到另一…

Python深度挖掘:openpyxl和pandas的使用詳細

文章目錄 一、Excel處理在數據分析中的重要性二、openpyxl基礎與核心功能2.1 openpyxl簡介與安裝2.2 工作簿與工作表的基本操作創建新工作簿打開已有工作簿工作表操作 2.3 單元格操作詳解基本單元格操作批量操作單元格特殊單元格操作 2.4 樣式與格式設置字體樣式對齊方式邊框設…

Android Q允許低內存啟用系統彈窗

如果SYSTEM_ALERT_WINDOW權限可用&#xff0c;則返回true。 *從Q開始&#xff0c;在低ram手機上禁用SYSTEM_ALERT_WINDOW。 vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/Utils.java public static boolean isSystemAlertWindowEnabled(Co…

taro小程序如何實現大文件(視頻、圖片)后臺下載功能?

一、需求背景 1、需要實現小程序下載最大500M視頻 2、同時需支持圖片下載 3、退到其他頁面再次回到當前頁面時&#xff0c;下載進度也需要展示 二、實現步驟 1、在app.ts文件定義一個全局變量globalDownLoadData 2、寫一個獨立的下載hooks&#xff0c;代碼如下&#xff08;…

BUUCTF——Online Tool

BUUCTF——Online Tool 進入靶場 <?phpif (isset($_SERVER[HTTP_X_FORWARDED_FOR])) {$_SERVER[REMOTE_ADDR] $_SERVER[HTTP_X_FORWARDED_FOR]; }if(!isset($_GET[host])) {highlight_file(__FILE__); } else {$host $_GET[host];$host escapeshellarg($host);$host e…

《解鎖CSS Flex布局:重塑現代網頁布局的底層邏輯》

網頁布局作為用戶體驗的基石&#xff0c;其重要性不言而喻。從早期簡單的表格布局&#xff0c;到后來基于浮動與定位的復雜嘗試&#xff0c;網頁布局技術始終在不斷演進。而CSS Flex布局的出現&#xff0c;宛如一顆璀璨的新星&#xff0c;徹底革新了網頁布局的設計理念與實踐方…

4.28-4.29 Vue

基于數據渲染出用戶看到的頁面。 常用指令&#xff1a; click單擊事件。 axios&#xff1a; 發出請求后&#xff0c;不會等待請求結束&#xff0c;而是繼續進行下面的代碼。

每日算法-250429

每日 LeetCode 題解 (2025-04-29) 大家好&#xff01;這是今天的 LeetCode 刷題記錄&#xff0c;主要涉及幾道可以使用貪心策略解決的問題。 2037. 使每位學生都有座位的最少移動次數 題目描述: 思路 貪心 解題過程 要使總移動次數最少&#xff0c;直觀的想法是讓每個學生…

yolov8+kalman 實現目標跟蹤統計人流量

簡述 最近接了畢業生的畢業設計題&#xff0c;想著幫幫忙&#xff0c;要使用機器視覺識別&#xff0c;追蹤和邏輯統計的方式來統計人流&#xff0c;要求是滿足下面特性 高精度&#xff1a;YOLOv8 提供高質量檢測&#xff0c;卡爾曼濾波平滑跟蹤。高效率&#xff1a;兩者結合滿…

Shopify網上商店GraphQL Admin接口查詢實戰

目錄 一、Shopify網上商店 二、個人商店配置接口權限 三、PostMan調用接口測試 四、通過Java服務調用接口 一、Shopify網上商店 Shopify是由Tobi Ltke創辦的加拿大電子商務軟件開發商&#xff0c;總部位于加拿大首都渥太華&#xff0c;已從一家在咖啡店辦公的 5人團隊&…

【Tips】高效文獻管理:Zotero 導入參考文獻的多種方式詳解

高效文獻管理&#xff1a;Zotero 導入參考文獻的多種方式詳解 在學術研究中&#xff0c;高效管理參考文獻是提升效率的關鍵。Zotero 作為一款強大的文獻管理工具&#xff0c;提供了多種便捷的文獻導入方式。以下結合文獻題錄完整性對比分析&#xff0c;為大家詳細介紹 Zotero …

[AI]browser-use + web-ui 大模型實現自動操作瀏覽器

[AI]browser-use web-ui 大模型實現自動操作瀏覽器 介紹 官方地址&#xff1a;https://github.com/browser-use/web-ui browser-use主要作用是將 AI Agent 與瀏覽器鏈接起來從而實現由 AI 驅動的瀏覽器自動化。今天會給大家介紹如何通過browser-use web-ui來搭建并操作browse…

Springboot請求靜態資源時,request.getServletPath() 返回error

大家好&#xff0c;我是 程序員碼遞夫。 SpringBoot請求靜態資源時&#xff0c;request.getServletPath() 返回error&#xff0c; 明明我的目錄文件是存在的怎么就報錯了呢&#xff1f; 如我請求 http://127.0.0.1:9090/Hanfu/upload/1647161536390.png 通常是因為請求的資…

在開發板上如何處理curl: (60) SSL certificate problem

目錄 引言 問題解析 解決方法 跳過證書驗證 采用證書認證 結語 引言 最近一直推薦學生們在課程實驗中使用curl及其libcurl。curl 是一個強大的命令行工具&#xff0c;用于在命令行中進行數據傳輸。它支持多種協議&#xff0c;如 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。…

CSRF請求偽造

該漏洞主要是關乎于用戶&#xff0c;告誡用戶不可亂點擊鏈接&#xff0c;提升自我防范&#xff0c;才能不落入Hacker布置的陷阱&#xff01; 1. cookie與session 簡單理解一下兩者作用 1.1. &#x1f36a; Cookie&#xff1a;就像超市的會員卡 存儲位置&#xff1a;你錢包里…

Python循環與遍歷詳解:從入門到進階

在Python編程中&#xff0c;循環和遍歷是最基礎但極其重要的知識點。理解并掌握這部分內容&#xff0c;是編寫高效、清晰代碼的前提。本文將從for循環和while循環的基本語法出發&#xff0c;逐步深入探討range、enumerate、zip、列表推導式、字典遍歷等Python中常見的遍歷技巧&…

Python-MCPServer開發

Python-MCPServer開發 使用FastMCP開發【SSE模式的MCPServer】&#xff0c;熟悉【McpServer編碼過程】【McpServer調試方法】 1-核心知識點 1-熟悉【SSE模式的MCPServer】開發2-熟悉【stdio模式的MCPServer】開發3-熟悉【啟動MCPServer】的三種方式 3.1-直接啟動:python mcp_s…