Linux:多線程---深入互斥淺談同步

文章目錄

      • 1. 互斥
        • 1.1 為什么需要互斥
        • 1.2 互斥鎖
        • 1.3 初談互斥與同步
        • 1.4 鎖的原理
        • 1.5 可重入VS線程安全
        • 1.6 死鎖
        • 1.7 避免死鎖的算法(擴展)

  • 序:在上一章中我們知道了線程控制的三個角度:線程創建、線程等待和線程終止,分別從接口以及參數的意義和功能的角度來了解,以及最后深入原生線程庫,了解用戶級線程與內核輕量型進程的關系。而本章將從線程的同步與互斥的角度來帶大家了解什么是互斥和同步,以及為什么要互斥和同步等一系列問題。

上一章線程控制的知識補充:

線程分離:
在這里插入圖片描述
pthread_detach函數,可以是線程組內其他線程對目標線程進行分離,也可以是線程自己分離,分離后的線程不可被等待,如果強行等待也會返回錯誤碼22。

問題一:為什么要有線程分離呢?

如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程資源。

歸根結底,我們讓線程分離,其實就是更改線程的原生線程庫里的tcb內的分離的屬性,而pthread_join就是識別到了該分離屬性被更改為已分離,所以才會直接返回一個錯誤碼。

1. 互斥

1.1 為什么需要互斥

多線程搶票模型代碼演示:

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
#include<vector>
using namespace std;#define NUM 4int ticket =100;//用多線程,模擬一輪搶票class ThreadData
{
public:ThreadData(int number){_thread_name ="thread-" + to_string(number);}
public:string _thread_name;
};void* GetTicket(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);const char* name =td->_thread_name.c_str();while(true){if(ticket>0){usleep(5000);printf("i am %s,get a ticket:%d\n",name,ticket);ticket--;}else break;}printf("%s ... quit\n",name);return nullptr;}
int main()
{vector<pthread_t> tids;vector<ThreadData*> thread_datas;for(int i=0;i<NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);thread_datas.push_back(td);pthread_create(&tid,nullptr,GetTicket,thread_datas[i]);tids.push_back(tid);}for(auto &e :tids){pthread_join(e,nullptr);}for(auto &e :thread_datas){delete e;}return 0;
}

結果如圖:

在這里插入圖片描述
那么問題來了搶票模型中,為什么搶票搶到最后,竟然搶到了負數?這個問題我們暫且不談,我們繼續往下說。

要想了解為什么會出現這樣的情況,我們首先就要知道既然ticket出現了負數,就說明ticket–出現了問題,共享數據------>數據不一致問題!!!(肯定和多線程并發訪問是有關系的),對一個全局變量進行多線程并發–或++操作是否是安全的?所以這個–操作不是原子的,所以也不是安全的。

既然–操作是不安全的,不是原子的,那我們要了解ticket–究竟要有哪些步驟:
在這里插入圖片描述

第一個步驟:先將ticket讀入到CPU的寄存器當中
第二個步驟:CPU內部進行–操作
第三個步驟:將計算結果寫回內存

但是想要了解為什么會出現這樣的情況,我們還要了解一個額外的知識點:寄存器不等于寄存器的內容線程在執行的時候,將共享數據,加載到CPU寄存器的本質:把數據的內容,變成了自己的上下文 — 這樣的數據以拷貝的形式給自己單獨拿了一份

既然ticket–是有三個步驟組成,如果在這三個步驟之內發生了線程切換就會導致數據不一致的問題!!!

在這里插入圖片描述
假設當前搶票的進程中,票有1000張,該進程內有兩個線程正在搶票,此時thread-1線程正在實現搶票,剛完成第一步,將內存中的數據讀入寄存器中,也就是讀入該線程的上下文中,如果此時來了第二個線程thread-2也要實行搶票,將thread-1線程切換了,thread-1線程就會帶著這個1000的數據一起離開,等待線程再次切換回來!!!
在這里插入圖片描述
當線程切換到thread-2線程,假設此時thread-2線程在下一次線程切換的時間片內進行了100次搶票的動作,此時的票數就由1000變成了900
在這里插入圖片描述

當thread-2線程搶了100張票后,將寄存中的900寫回給內存中的ticket,此時thread-1線程切換回來了,thread-2線程就要帶著自己的硬件上下文走,于是thread-2就將寄存器中的900帶走了,thread-1線程切換回來后,會將之前就帶走的寄存器中的1000帶回來,在放入到寄存器中,即恢復上下文。
在這里插入圖片描述
然后,此時thread-1會繼續執行未完成的動作,繼續執行第二步和第三步。
在這里插入圖片描述
這就會導致,thread-2辛辛苦苦搶的票,將1000變成900,結果thread-1線程切換回來后就變成了999。

現在讓我們來回答最開始的那個問題,為什么ticket會出現負數?
在這里插入圖片描述
假設此時的實際票數小于線程數,此時有四個線程,但票只有兩個了,別忘了ticket>0也是運算(邏輯運算)所以此時同時有4個線程都對ticket進行了邏輯運算,此時票有兩個,都是大于0,此時四個線程都進入了該循環內,都可以進行ticket–,這也就是為什么會出現了負數!!!

1.2 互斥鎖

怎么解決上述的一些列問題???
對共享數據的任何訪問,保證任何時候只有一個執行流進行訪問!—互斥!!!
而想要實現互斥就要引入互斥鎖的概念!!!

在這里插入圖片描述

鎖資源的定義,初始化和釋放:

pthread_mutex_t是庫提供的一種數據類型
pthread_mutex_init(第一個參數傳鎖,第二個參數傳鎖的各種參數(默認傳nullptr))
pthread_mutex_destroy
(如果用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALZER進行全局變量的初始化,就不用調用pthread_mutex_destroy函數進行釋放)

一種臨界資源,由多個線程訪問,如果想要保證臨界資源的安全,就必須讓這個多個線程訪問同一把鎖!!!

在這里插入圖片描述

鎖的申請和釋放:

pthread_mutex_lock
pthread_mutex_unlock
其中的pthread_mutex_trylock函數就是加鎖的非阻塞版本

到這一步,大家只能對鎖有個印象,沒法深刻知道鎖的作用和鎖的使用,接下來我將改進原本的多線程搶票模型的代碼,用互斥鎖來使原版中的多線程導致的數據不一致問題得到解決!!!

互斥鎖版的多線程搶票模型代碼演示

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
#include<vector>using namespace std;#define NUM 5int ticket =100;//用多線程,模擬一輪搶票class ThreadData
{
public:ThreadData(int number,pthread_mutex_t* lock){_thread_name ="thread-" + to_string(number);_lock=lock;}
public:string _thread_name;pthread_mutex_t *_lock;
};void* GetTicket(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);const char* name =td->_thread_name.c_str();while(true){pthread_mutex_lock(td->_lock);if(ticket > 0){//usleep(5000);printf("i am %s,get a ticket:%d\n",name,ticket);ticket--;}else{pthread_mutex_unlock(td->_lock);break;}pthread_mutex_unlock(td->_lock);usleep(5000);}printf("%s ... quit\n",name);return nullptr;
}int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock,nullptr);vector<pthread_t> tids;vector<ThreadData*> thread_datas;for(int i=0;i<NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i,&lock);thread_datas.push_back(td);pthread_create(&tid,nullptr,GetTicket,thread_datas[i]);tids.push_back(tid);}for(auto &e :tids){pthread_join(e,nullptr);}for(auto &e :thread_datas){delete e;}pthread_mutex_destroy(&lock);return 0;
}

對于上面一段代碼,我們用鎖將臨界資源鎖住,同一時間只能有一個線程進行訪問,從而實現對臨界資源的保護

其中,被鎖保護的資源叫做臨界資源,某幾段訪問臨界資源的代碼區叫做臨界區

加鎖的本質:是用時間來換安全
加鎖的表現:線程對臨界區代碼的串行執行
加鎖原則:盡量保證臨界區代碼越少越好。
申請鎖成功了,才能往后執行,不成功,就會阻塞等待(等待鎖資源釋放)

不同線程對于鎖的競爭能力可能會不同,在純互斥環境中,如果鎖分配不夠合理,容易導致其他線程的饑餓問題----->不是說只要有互斥就必有饑餓,適合純互斥場景就用互斥。

1.3 初談互斥與同步

目前,我們對于鎖的概念已經有了一個清晰的認識了,但是我們發現了一個新的問題,當一個線程申請鎖,完成對臨界資源的訪問后,釋放鎖后,該線程可能也會申請鎖,這就可能出現一個線程一直在申請和釋放鎖,導致其他線程沒辦法申請到鎖,對于這種情況,就要深入了解同步的概念來解決新出現的問題,現在讓我們更加深入的了解互斥與同步吧

現在有一個vip自習室:
在這里插入圖片描述
vip自習室規定:1. 外面的同學想要進入vip自習室必須排隊。2. 出來的同學,將鑰匙放好后,不能立馬重新拿鑰匙進vip自習室,如果想要再次進入自習室則必須排到隊列的尾部進行排隊

vip自習室的規則讓所有的同學(線程)按照一定的順序拿到鑰匙(鎖)進入vip自習室,而按照一定的順序性獲取資源的模式就是同步!!!

1.4 鎖的原理

鎖本身也是一種臨界資源!!!所以。申請鎖和釋放鎖本身就被設計成為了原子性操作了(問題:如何做到的???)

在臨界區中,線程可以被切換嗎?可以切換!!!在線程被切出去的時候,是持有鎖被切走的。該線程即使被切換走了,照樣沒有任何線程能進入資源臨界區訪問臨界資源!

對于其他線程來講,一個線程要么沒有鎖,要么釋放鎖。當前線程訪問臨界區的過程,對于其他線程就是原子的!!!

問題一:為什么說ticket–不是原子的?因為該語句會變成多條匯編語句,在該匯編語句的中間,如果有其他線程也在執行,就會出錯,出現不一致的情況,換言之,只要匯編語句只有一條就沒有問題,所以,原子:只有一條匯編語句就是原子的!!!

lock的匯編語句:xchqb %al,mutex
Xchqb交換的本質:把內存中的數據(共享),交換到CPU的寄存器中(把數據交換到線程的硬件的上下文中)—>把一個共享的鎖,讓一個線程以一條匯編語句的方式,交換到自己的上下文中!!!(這就叫做當前線程持有鎖了!!!)

unlock的匯編語句:movb $1,mutex:將一個共享的鎖從線程的上下文中拿出來。(這就叫做當前線程釋放鎖了!!!)

1.5 可重入VS線程安全

線程安全的概念:多個線程并發同一段代碼時,不會出現不同的結果。常見對全局變量或者靜態變量進行操作, 并且沒有有鎖保護的情況下,會出現該問題。
重入的概念:同一個函數被不同的執行流調用,當前一個流程還沒有執行完,就有其他的執行流再次進入,我們稱之為重入。一個函數在重入的情況下,運行結果不會出現任何不同或者任何問題,則該函數被稱為可重入函數、不則具不可重入函數。

可重入與線程安全聯系:

函數是可重入的,那就是線程安全的。
函數是不可重入的,那就不能由多個線程使用,有可能引發線程安全問題。
如果一個函數中有全局變量,那么這個函數既不是線程安全也不是可重入的。

可重入與線程安全區別:

可重入函數是線程安全函數的一種。
線程安全不一定是可重入的,而可重入函數則一定是線程安全的。
如果將對臨界資源的訪問加上鎖,則這個函數是線程安全的,但如果這個重入函數若鎖還未釋放則會產生死鎖,因此是不可重入的。

1.6 死鎖

死鎖的概念:

死鎖是指在一組進程中的各個進程均占有不會釋放的資源,但因互相申請被其他進程所站用不會釋放的資源而處于的一種永久等待狀態。

死鎖產生的必要條件:

互斥條件:一個資源每次只能被一個執行流使用(前提)
請求與保持條件:一個執行流因請求資源而阻塞時,對已獲得的資源保持不放(原則)
不剝奪條件:一個執行流已獲得的資源,在末使用完之前,不能強行剝奪(原則)
死鎖產生的充分條件:
循環等待條件:若干執行流之間形成一種頭尾相接的循環等待資源的關系(重要條件)
這三個必要條件必須同時滿足才是死鎖。

如何避免死鎖問題?

1. 破壞死鎖的四個必要條件,只需要一個不滿足就可以了
2. 加鎖順序一致
3. 避免鎖未釋放的場景
4. 資源一次性分配

1.7 避免死鎖的算法(擴展)

銀行家算法:

下面是銀行家算法的模擬實現,感興趣的小伙伴可以去了解

銀行家算法避免死鎖

總結:

本篇博客先是補充了上一章中對于線程分離的知識缺失的內容補全,而后,從一小段代碼出發,在多線程的搶票模型下,我們逐步發現多線程帶來的問題,并逐步解決,為了解決這些問題,我們先后引入了互斥和同步的概念,最后有隊線程安全問題和可重入的問題進行了了解,并講述了死鎖的概念及其產生的條件,最后以避免死鎖的銀行家算法結尾,謝謝大家的支持!!!

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

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

相關文章

適用于 vue2、vue3 的自定義指定:v-int(正整數)

在項目中&#xff0c;我們經常會遇到輸入框只允許輸入數字的情況&#xff0c;下面是一段自定義指定 代碼&#xff0c;復制到項目中&#xff0c;注冊指定即可使用用法如下&#xff1a; 創建一個IntInput.js 文件&#xff0c;將下面代碼復制到文件中保存在項目中的 main.js 文件中…

學習基于springboot秒殺系統-環境配置(接口封裝,mybatis,mysql,redis(Linux))

文章目錄前言創建springboot項目封裝controller層輸入輸出rest api 的json輸出返回頁面集成mybatis集成redis下載虛擬機和centos下載redis.tar.gz上傳redis.tar.gz 到虛擬機前言 今天開始記錄學習秒殺系統-課程是基于慕課上的搜索秒殺系統的課程&#xff0c;老師講解非常好。這…

stm32達到什么程度叫精通?

STM32達到什么程度叫精通&#xff1f;一個十年老兵的深度反思 前言&#xff1a;精通二字&#xff0c;重如泰山 每次有人問我"STM32達到什么程度叫精通"這個問題&#xff0c;我都會沉默很久。 不是因為這個問題難回答&#xff0c;而是因為"精通"這兩個字太重…

微軟上線Deep Research:OpenAI同款智能體,o3+必應雙王炸

今天凌晨&#xff0c;微軟在官網宣布&#xff0c;Azure AI Foundry中上線Deep Research公開預覽版。這是支持API和SDK的OpenAI 高級智能體研究能力產品&#xff0c;并且Azure 的企業級智能體平臺完全集成。Deep Research是OpenAI在今年4月25日發布的最新產品&#xff0c;能夠像…

Spring Batch終極指南:原理、實戰與性能優化

&#x1f31f; Spring Batch終極指南&#xff1a;原理、實戰與性能優化單機日處理10億數據&#xff1f;揭秘企業級批處理架構的核心引擎&#xff01;一、Spring Batch 究竟是什么&#xff1f;Spring batch是用于創建批處理應用程序&#xff08;執行一系列作業&#xff09;的開源…

【Part 3 Unity VR眼鏡端播放器開發與優化】第四節|高分辨率VR全景視頻播放性能優化

文章目錄《VR 360全景視頻開發》專欄Part 3&#xff5c;Unity VR眼鏡端播放器開發與優化第一節&#xff5c;基于Unity的360全景視頻播放實現方案第二節&#xff5c;VR眼鏡端的開發適配與交互設計第三節&#xff5c;Unity?VR手勢交互開發與深度優化第四節&#xff5c;高分辨率V…

TCP/IP協議基礎

TCPIP協議基礎 網絡模型 -OSI參考模型 -OSI參考模型各層功能 -TCP/IP網絡模型 -TCP/IP協議棧OSI參考模型 – 為了解決網絡設備之間的兼容性問題&#xff0c;國際標準化組織ISO于1984年提出了OSI RM&#xff08;開放系統互連參考模型&#xff09;。 OSI參考模型一共有七層&#…

【Nginx】Nginx代理WebSocket

1.websocketWebSocket 是一種網絡通信協議&#xff0c;它提供了在單個 TCP 連接上進行全雙工&#xff08;雙向&#xff09;通信的能力假設需求&#xff1a;把 ws://192.168.0.1:8088/ws-api/websocket/pushData代理到ws://192.168.0.156:8888/websocket/pushData&#xff1b;同…

Spring AI Alibaba Graph使用案例人類反饋

1、Spring AI Alibaba Graph 是社區核心實現之一&#xff0c;也是整個框架在設計理念上區別于 Spring AI 只做底層原子抽象的地方&#xff0c;Spring AI Alibaba 期望幫助開發者更容易的構建智能體應用。基于 Graph 開發者可以構建工作流、多智能體應用。Spring AI Alibaba Gra…

本地部署jenkins持續集成

一、準備環境&#xff08;jdk版本跟Tomcat版本要匹配&#xff09; java jdk 環境(版本是11.0.21) jenkins war包(版本是2.440.3) Tomcat (版本是 9.0.84) 二、安裝步驟 1、安裝jdk環境 1&#xff09;先安裝java環境&#xff0c;安裝完成后配置環境變量&#xff0c;參考上…

基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一個WebUI自動化框架(1)搭建框架基本雛形

本次框架使用Maven作為代碼構建管理&#xff0c;引用了PO模式&#xff0c;將整體的代碼分成了頁面層、用例層、業務邏輯層。框架搭建流程&#xff1a;1、在pom.xml中引入依賴&#xff1a;<!-- https://mvnrepository.com/artifact/io.appium/java-client --> <depende…

從零構建MCP服務器:FastMCP實戰指南

引言&#xff1a;MCP協議與FastMCP框架 Model Context Protocol&#xff08;MCP&#xff09;是連接AI模型與外部服務的標準化協議&#xff0c;允許LLM&#xff08;如Claude、Gemini&#xff09;調用工具、訪問數據。然而&#xff0c;直接實現MCP協議需要處理JSON-RPC、會話管理…

基于FPGA的智能小車設計(包含代碼)/ 全棧FPGA智能小車:Verilog實現藍牙/語音/多傳感器融合的移動平臺

首先先聲明一下&#xff0c;本項目已經歷多輪測試&#xff0c;可以放心根據我的設計進行二次開發和直接套用&#xff01;&#xff01;&#xff01; 代碼有詳細的注釋&#xff0c;方便同學進行學習&#xff01;&#xff01; 制作不易&#xff0c;記得三連哦&#xff0c;給我動…

Object.defineProperties 詳解

Object.defineProperties 詳解 Object.defineProperties 是 JavaScript 中用于在一個對象上定義或修改多個屬性的方法。它是 Object.defineProperty 的復數版本&#xff0c;允許你一次性定義多個屬性。 基本語法 Object.defineProperties(obj, props)obj&#xff1a;要在其上定…

MyBatis-Plus:深入探索與最佳實踐

MyBatis-Plus作為MyBatis的增強版&#xff0c;已經在Java開發中得到了廣泛應用。它不僅繼承了MyBatis的所有功能&#xff0c;還提供了許多強大的擴展功能&#xff0c;幫助開發者提升開發效率和代碼質量。本文將深入探討MyBatis-Plus的高級特性及其在實際項目中的最佳實踐。一、…

勞斯萊斯數字孿生技術:重構航空發動機運維的綠色革命

在航空工業邁向智能化的浪潮中&#xff0c;勞斯萊斯以數字孿生技術為核心&#xff0c;構建了發動機全生命周期管理的創新范式。這項技術不僅重新定義了航空發動機的維護策略&#xff0c;更通過數據驅動的決策體系&#xff0c;實現了運營效率與生態效益的雙重突破。本文將從技術…

NPM組件 querypilot 等竊取主機敏感信息

【高危】NPM組件 querypilot 等竊取主機敏感信息 漏洞描述 當用戶安裝受影響版本的 querypilot 等NPM組件包時會竊取用戶的主機名、用戶名、工作目錄、IP地址等信息并發送到攻擊者可控的服務器地址。 MPS編號MPS-2kgq-v17b處置建議強烈建議修復發現時間2025-07-05投毒倉庫np…

創業商業融資計劃書PPT模版

創業商業融資計劃書PPT模版&#xff1a;https://pan.quark.cn/s/25a043e4339e

解決GitHub倉庫推送子文件夾后打不開的問題

從你描述的情況來看&#xff0c;IELTS_AI_Assessment 很可能被識別為了 Git 子模塊&#xff08;submodule&#xff09;&#xff0c;而不是普通文件夾&#xff0c;這會導致在 GitHub 上無法直接打開查看內容。以下是具體原因和解決辦法&#xff1a;為什么文件夾無法打開&#xf…