新聞中心
隨著計算機硬件的高速發(fā)展和軟件復(fù)雜程度的增加,多線程編程成為了現(xiàn)代操作系統(tǒng)和應(yīng)用程序開發(fā)中的必備技術(shù)。同時,多線程編程也帶來了一些新的問題,例如多線程之間的資源競爭、數(shù)據(jù)同步等。為了解決這些問題,操作系統(tǒng)提供了一些線程同步的機制,比如互斥鎖、信號量、條件變量等。本文主要介紹Linux互斥鎖的封裝,以保障多線程同步安全。

1. 互斥鎖與多線程同步
互斥鎖是一種線程同步的機制,它可以確保在任意時刻只有一個線程訪問共享資源,從而避免多個線程同時訪問共享資源而導(dǎo)致的數(shù)據(jù)錯誤。具體來說,當一個線程需要訪問共享資源時,它先嘗試獲取互斥鎖,如果鎖已經(jīng)被其他線程獲取,則當前線程會阻塞等待鎖被釋放。當鎖被釋放后,當前線程再次嘗試獲取鎖,如果獲取成功,則該線程可以開始訪問共享資源。
互斥鎖的使用需要遵守以下幾個原則:
1) 盡量縮小互斥鎖保護的數(shù)據(jù)范圍,避免不必要的線程阻塞和鎖競爭;
2) 確保每個訪問共享資源的線程都正確地獲取和釋放互斥鎖;
3) 避免死鎖或饑餓問題,即在持有鎖的情況下不能阻塞其他線程或?qū)е缕渌€程無法獲取鎖。
2. Linux互斥鎖的封裝
在Linux系統(tǒng)下,互斥鎖的使用需要依賴pthread.h頭文件和相關(guān)庫函數(shù)。然而,直接使用互斥鎖有時會存在一些問題,例如使用不當容易導(dǎo)致死鎖或饑餓問題,而且代碼冗長,不便于維護。
為了解決這些問題,我們可以對互斥鎖進行封裝,以提供更為安全、簡單、易用的接口。以下是一個簡單的互斥鎖封裝示例:
“`c
#include
#include
class Mutex {
public:
Mutex() {
pthread_mutex_init(&mutex_, NULL);
}
~Mutex() {
pthread_mutex_destroy(&mutex_);
}
void lock() {
pthread_mutex_lock(&mutex_);
}
void unlock() {
pthread_mutex_unlock(&mutex_);
}
private:
pthread_mutex_t mutex_;
};
“`
上述代碼定義了一個名為Mutex的類,這個類封裝了一個互斥鎖pthread_mutex_t mutex_。類的構(gòu)造函數(shù)和析構(gòu)函數(shù)分別對互斥鎖進行初始化和銷毀。類中還定義了lock()和unlock()方法,用于獲取和釋放互斥鎖。
使用上述封裝后的互斥鎖,我們可以更加方便地實現(xiàn)多線程同步。例如,以下代碼展示了一個線程安全的計數(shù)器實現(xiàn):
“`c
#include
#include
#include “Mutex.h”
int count = 0;
Mutex g_mutex;
void worker() {
for (int i = 0; i
g_mutex.lock();
++count;
g_mutex.unlock();
}
}
int mn() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
std::cout
return 0;
}
“`
上述代碼中,我們定義了一個全局的計數(shù)器count和一個Mutex對象g_mutex,并創(chuàng)建了兩個線程執(zhí)行worker()函數(shù)。在worker()函數(shù)中,我們使用互斥鎖保護計數(shù)器count的訪問,以避免多線程訪問的競爭和錯誤。
3.
相關(guān)問題拓展閱讀:
- Linux下各種鎖的理解和使用及總結(jié)解決epoll驚群問題(面試???-
- linux下信號量和互斥鎖的區(qū)別
Linux下各種鎖的理解和使用及總結(jié)解決epoll驚群問題(面試???-
鎖出現(xiàn)的原因
臨舉態(tài)界資源是什么: 多線程執(zhí)行流所共享的資源
鎖的作用是什么, 可以做原子操作, 在多線程中針對臨界資源的互斥訪問… 保證一個時刻只有一個線程可以持有鎖對于臨界資源做修改操作…
任何一個線程如果需要修改,向臨界資源做寫入操作都必須持有鎖,沒有持有鎖就不能對于臨界資源做寫入操作.
鎖 : 保證同一時刻只能有一個線程對于臨界資源做寫入操作 (鎖地功能)
再一個直觀地代碼引出問題,再從指令集的角度去看問題
上述一個及其奇怪的結(jié)果,這個結(jié)果每一次運行都可能是不一樣的,Why ? 按照我們本來的想法是每一個線程 +結(jié)果肯定應(yīng)該是呀,可以就是達不到這個值
為何? (深入?yún)R編指令來看) 一定將過程放置到匯編指令上去看就可以理解這個過程了.
a++; 或者 a += 1; 這些操作的匯編操作是幾個步驟?
其實是三個步驟:
正常情況下,數(shù)據(jù)少,操作的線程少,問跡叢題倒是不大,想一想要是這樣的情況下,操作次數(shù)大,對齊操作的線程多,有些線程從中間切入進來了,在運算之后還沒寫回內(nèi)存就另外一個線程切入進來同時對于之前的數(shù)據(jù)進行++ 再寫回內(nèi)存, 啥效果,多次++ 操作之后結(jié)果確實一次加加操作后的結(jié)果。 這樣的操作 (術(shù)語叫做函數(shù)的重入) 我覺得其實就是重入到了匯編指令中間了,還沒將上一次運算的結(jié)果寫回內(nèi)存就重新對這個內(nèi)存讀取再運算寫姿答櫻入,結(jié)果肯定和正常的邏輯后的結(jié)果不一樣呀
來一幅圖片解釋一下
咋辦? 其實問題很清楚,我們只需要處理的是多條匯編指令不能讓它中間入其他的線程運算. (要想自己在執(zhí)行匯編指令的時候別人不插入進來) 將多條匯編指令綁定成為一條指令不就OK了嘛。
也就是原子操作?。。?/p>
不會原子操作?操作系統(tǒng)給咱提供了線程的 綁定方式工具呀:mutex 互斥鎖(互斥量), 自旋鎖(spinlock), 讀寫鎖(readers-writer lock) 他們也稱作悲觀鎖. 作用都是一個樣,將多個匯編指令鎖成為一條原子操作 (此處的匯編指令也相當于如下的臨界資源)
悲觀鎖:鎖如其名,每次都悲觀地認為其他線程也會來修改數(shù)據(jù),進行寫入操作,所以會在取數(shù)據(jù)前先加鎖保護,當其他線程想要訪問數(shù)據(jù)時,被阻塞掛起
樂觀鎖:每次取數(shù)據(jù)的時候,總是樂觀地認為數(shù)據(jù)不會被其他線程修改,因此不上鎖。但是在更新數(shù)據(jù)前, 會判斷其他數(shù)據(jù)在更新前有沒有對數(shù)據(jù)進行修改。
互斥鎖
最為常見使用地鎖就是互斥鎖, 也稱互斥量. mutex
特征,當其他線程持有互斥鎖對臨界資源做寫入操作地時候,當前線程只能掛起等待,讓出CPU,存在線程間切換工作
解釋一下存在線程間切換工作 : 當線程試圖去獲取鎖對臨界資源做寫入操作時候,如果鎖被別的線程正在持有,該線程會保存上下文直接掛起,讓出CPU,等到鎖被釋放出來再進行線程間切換,從新持有CPU執(zhí)行寫入操作
互斥鎖需要進行線程間切換,相比自旋鎖而言性能會差上許多,因為自旋鎖不會讓出CPU, 也就不需要進行線程間切換的步驟,具體原理下一點詳述
加互斥量(互斥鎖)確實可以達到要求,但是會發(fā)現(xiàn)運行時間非常的長,因為線程間不斷地切換也需要時間, 線程間切換的代價比較大.
相關(guān)視頻推薦
你繞不開的組件—鎖,4個方面手撕鎖的多種實現(xiàn)
“驚群”原理、鎖的設(shè)計方案及繞不開的“死鎖”問題
學(xué)習(xí)視頻教程-騰訊課堂
需要C/C++ Linux服務(wù)器架構(gòu)師學(xué)習(xí)資料加qun獲?。ㄙY料包括
C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg
等),免費分享
自旋鎖
spinlock.自旋鎖.
對比互斥量(互斥鎖)而言,獲取自旋鎖不需要進行線程間切換,如果自旋鎖正在被別的線程占用,該線程也不會放棄CPU進行掛起休眠,而是恰如其名的在哪里不斷地循環(huán)地查看自旋鎖保持者(持有者)是否將自旋鎖資源釋放出來… (自旋地原來就是如此)
口語解釋自旋:持有自旋鎖的線程不釋放自旋鎖,那也沒有關(guān)系呀,我就在這里不斷地一遍又一遍地查詢自旋鎖是否釋放出來,一旦釋放出來我立馬就可以直接使用 (因為我并沒有掛起等待,不需要像互斥鎖還需要進行線程間切換,重新獲取CPU,保存恢復(fù)上下文等等操作)
哪正是因為上述這些特點,線程嘗試獲取自旋鎖,獲取不到不會采取休眠掛起地方式,而是原地自旋(一遍又一遍查詢自旋鎖是否可以獲?。┬适沁h高于互斥鎖了. 那我們是不是所有情況都使用自旋鎖就行了呢,互斥鎖就可以放棄使用了嗎????
解釋自旋鎖地弊端:如果每一個線程都僅僅只是需要短時間獲取這個鎖,那我自旋占據(jù)CPU等待是沒啥問題地。要是線程需要長時間地使用占據(jù)(鎖)。。。 會造成過多地?zé)o端占據(jù)CPU資源,俗稱站著茅坑不拉屎… 但是要是僅僅是短時間地自旋,平衡CPU利用率 + 程序運行效率 (自旋鎖確實是在有些時候更加合適)
自旋鎖需要場景:內(nèi)核可搶占或者P(多處理器)情況下才真正需求 (避免死鎖陷入死循環(huán),瘋狂地自旋,比如遞歸獲取自旋鎖. 你獲取了還要獲取,但是又沒法釋放)
自旋鎖的使用函數(shù)其實和互斥鎖幾乎是一摸一樣地,僅僅只是需要將所有的mutex換成spin即可
僅僅只是在init存在些許不同
何為驚群,池塘一堆, 我瞄準一條插過去,但是好似所有的都像是覺著自己正在一樣的四處逃竄。 這個就是驚群的生活一點的理解
驚群現(xiàn)象其實一點也不少,比如說 accept pthread_cond_broadcast 還有多個線程共享epoll監(jiān)視一個listenfd 然后此刻 listenfd 說來 SYN了,放在了SYN隊列中,然后完成了三次握手放在了 accept隊列中了, 現(xiàn)在問題是這個connect我應(yīng)該交付給哪一個線程處理呢.
多個epoll監(jiān)視準備工作的線程 就是這群 (),然后connet就是魚叉,這一叉下去肯定是所有的 epoll線程都會被驚醒 (多線程共享listenfd引發(fā)的epoll驚群)
同樣如果將上述的多個線程換成多個進程共享監(jiān)視 同一個 listenfd 就是(多進程的epoll驚群現(xiàn)象)
咱再畫一個草圖再來理解一下這個驚群:
如果是多進程道理是一樣滴,僅僅只是將所有的線程換成進程就OK了
終是來到了今天的正題了: epoll驚群問題地解決上面了…
首先 先說說accept的驚群問題,沒想到吧accept 平時大家寫它的多線程地時候,多個線程同時accept同一個listensock地時候也是會存在驚群問題地,但是accept地驚群問題已經(jīng)被Linux內(nèi)核處理了: 當有新的連接進入到accept隊列的時候,內(nèi)核喚醒且僅喚醒一個進程來處理
但是對于epoll的驚群問題,內(nèi)核卻沒有直接進行處理。哪既然內(nèi)核沒有直接幫我們處理,我們應(yīng)該如何針對這種現(xiàn)象做出一定的措施呢?
驚群效應(yīng)帶來的弊端: 驚群現(xiàn)象會造成epoll的偽喚醒,本來epoll是阻塞掛起等待著地,這個時候因為掛起等待是不會占用CPU地。。。 但是一旦喚醒就會占用CPU去處理發(fā)生地IO事件, 但是其實是一個偽喚醒,這個就是對于線程或者進程的無效調(diào)度。然而進程或者線程地調(diào)取是需要花費代價地,需要上下文切換。需要進行進程(線程)間的不斷切換… 本來多核CPU是用來支持高并發(fā)地,但是現(xiàn)在卻被用來無效地喚醒,對于多核CPU簡直就是一種浪費 (浪費系統(tǒng)資源) 還會影響系統(tǒng)的性能.
解決方式(一般是兩種)
Nginx的解決方式:
加鎖:驚群問題發(fā)生的前提是多個進程(線程)監(jiān)聽同一個套接字(listensock)上的事件,所以我們只讓一個進程(線程)去處理監(jiān)聽套接字就可以了。
畫兩張圖來理解一下:
上述還沒有進行一個每一個進程都對應(yīng)一個listensock 而是多線程共享一個listensock 運行結(jié)果如下
所有的線程同時被喚醒了,但是實際上會處理連接的僅僅只是一個線程,
咱僅僅只是將主線程做如上這樣一個簡單的修改,每一個線程對應(yīng)一個listensock;每一個線程一個獨有的監(jiān)視窗口,將問題拋給內(nèi)核去處理,讓內(nèi)核去負載均衡 : 結(jié)果如下
僅僅喚醒一個線程來進行處理連接,解決了驚群問題
本文通過介紹兩種鎖入手,以及為什么需要鎖,鎖本質(zhì)就是為了保護,持有鎖你就有權(quán)力有能力操作寫入一定的臨界保護資源,沒有鎖你就不行需要等待,本質(zhì)其實是將多條匯編指令綁定成原子操作
然后介紹了驚群現(xiàn)象,通過一個巧妙地例子,扔一顆石子,只是瞄準一條魚扔過去了,但是整池魚都被驚醒了,
對應(yīng)我們地實際問題就是, 多個線程或者進程共同監(jiān)視同一個listensock。。。。然后IO連接事件到來地時候本來僅僅只是需要一個線程醒過來處理即可,但是卻會使得所有地線程(進程)全部醒過來,造成不必要地進程線程間切換,多核CPU被浪費喔,系統(tǒng)資源被浪費
處理方式 一。 Nginx 源碼加互斥鎖處理。。 二。設(shè)置SO_REUSEPORT, 使得多個進程線程可以同時連接同一個port , 為每一個進程線程搞一個listensock… 將問題拋給內(nèi)核去處理,讓他去負載均衡地僅僅將IO連接事件分配給一個進程或線程
linux下信號量和互斥鎖的區(qū)別
信號量用在多線程多任務(wù)同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作(大家都族滲團在semtake的時候,就阻塞在哪里)。
而互斥鎖是用在多線程多任務(wù)互斥的,一個線程占用了某一個資源,那么別的線程就無法訪問,直到這個線程unlock,其他的線程才開始可以利用這個資源。比如對全局變量的訪問,有時要加鎖,操作完喊蔽了,在解鎖。
有的時候鎖和信號量會同時使用的兆橘。我記得以前做的一個項目就是既有semtake,又有l(wèi)ock。
信號量與互斥鎖之間的區(qū)冊跡別:
1.
互斥量用于線程的互斥,信號量用于線程的同步。
這是互斥量和信號量的根本區(qū)別,也就是互斥和同步之間的區(qū)別。
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無數(shù)肆序的。
同步:是指在互州畢并斥的基礎(chǔ)上(大多數(shù)情況),通過其它機制實現(xiàn)訪問者對資源的有序訪問。在大多數(shù)情況下,同步已經(jīng)實現(xiàn)了互斥,特別是所有寫入資源的情況必定是互斥的。少數(shù)情況是指可以允許多個訪問者同時訪問資源
2.
互斥量值只能為0/1,信號量值可以為非負整數(shù)。
也就是說,一個互斥量只能用于一個資源的互斥訪問,它不能實現(xiàn)多個資源的多線程互斥問題。信號量可以實現(xiàn)多個同類資源的多線程互斥和同步。當信號量為單值信號量是,也可以完成一個資源的互斥訪問。
3.
互斥量的加鎖和解鎖必須由同一線程分別對應(yīng)使用,信號量可以由一個線程釋放,另一個線程得到。
關(guān)于linux互斥鎖封裝的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關(guān)注本站。
創(chuàng)新互聯(lián)網(wǎng)絡(luò)推廣網(wǎng)站建設(shè),網(wǎng)站設(shè)計,網(wǎng)站建設(shè)公司,網(wǎng)站制作,網(wǎng)頁設(shè)計,1500元定制網(wǎng)站優(yōu)化全包,先排名后付費,已為上千家服務(wù),聯(lián)系電話:13518219792
新聞名稱:Linux互斥鎖封裝:保障多線程同步安全(linux互斥鎖封裝)
文章網(wǎng)址:http://www.5511xx.com/article/dpgojoj.html


咨詢
建站咨詢
