Linux線程互斥之線程加鎖的使用詳解
一、鎖的定義
線程加鎖是在多線程編程環(huán)境中,為了確保在同一時(shí)刻只有一個(gè)線程能夠訪問特定的共享資源或執(zhí)行特定的代碼段,而采取的一種同步手段,通過在需要保護(hù)的資源或代碼段前獲取鎖,在訪問完成后釋放鎖,來實(shí)現(xiàn)對共享資源的互斥訪問
二、庫函數(shù)
1、初始化互斥鎖
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 返回值:成功返回0,失敗返回非零錯(cuò)誤碼
mutex
:表示要初始化的互斥鎖,pthread_mutex_t
是POSIX線程庫中定義的互斥鎖類型attr
:包含互斥鎖的屬性,設(shè)置為NULL
表示使用默認(rèn)屬性
2、銷毀互斥鎖
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯(cuò)誤碼
mutex
:表示要銷毀的互斥鎖
3、加鎖
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯(cuò)誤碼
mutex
:表示要加鎖的互斥鎖
4、解鎖
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯(cuò)誤碼
mutex
:表示要解鎖的互斥鎖
5、示例
#include <iostream> #include <pthread.h> #include <vector> #include <cstdio> #include <unistd.h> using namespace std; //定義一個(gè)全局鎖就可以不需要初始化和銷毀鎖的函數(shù)了 //pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; #define NUM 4 //共500張票 int tickets = 500; class ThreadInfo { public: ThreadInfo(const string &threadname, pthread_mutex_t *lock) :threadname_(threadname) ,lock_(lock) {} public: string threadname_; pthread_mutex_t *lock_; }; void *GrabTickets(void *args) { ThreadInfo *ti = static_cast<ThreadInfo*>(args); string name(ti->threadname_); while(true) { pthread_mutex_lock(ti->lock_); // 加鎖 if(tickets > 0) { usleep(10000); printf("%s get a ticket: %d\n", name.c_str(), tickets); tickets--; pthread_mutex_unlock(ti->lock_); // 解鎖 } else { pthread_mutex_unlock(ti->lock_); // 解鎖 break; } //這里上面的代碼 usleep(13); // 用休眠來模擬搶到票的后續(xù)動(dòng)作 } printf("%s quit...\n", name.c_str()); } int main() { pthread_mutex_t lock; // 定義互斥鎖 pthread_mutex_init(&lock, nullptr); // 初始化互斥鎖 vector<pthread_t> tids; vector<ThreadInfo*> tis; for(int i = 1; i <= NUM; i++) { pthread_t tid; ThreadInfo *ti = new ThreadInfo("Thread-"+to_string(i), &lock); pthread_create(&tid, nullptr, GrabTickets, ti); tids.push_back(tid); tis.push_back(ti); } // 等待所有線程 for(auto tid : tids) { pthread_join(tid, nullptr); } // 釋放資源 for(auto ti : tis) { delete ti; } // 銷毀互斥鎖 pthread_mutex_destroy(&lock); return 0; }
這樣就不會(huì)出現(xiàn)好多線程搶到一張票或者搶到不存在的票的問題了
三、深入理解鎖
1、解讀鎖的機(jī)制
(一)先入為主原則
我們將上方代碼中表示搶到票后續(xù)動(dòng)作的休眠代碼注釋掉再次執(zhí)行程序我們會(huì)發(fā)現(xiàn),都是線程1搶的票,多次執(zhí)行代碼之后發(fā)現(xiàn)這是概率性問題,但是在搶票的時(shí)候,有一段時(shí)間的票都是一個(gè)線程搶到的,我們預(yù)想的應(yīng)該是幾乎平均分配的樣子
這說明了幾個(gè)問題:
- 第一,線程對于鎖的競爭能力不同,一定有一個(gè)首先搶到鎖的線程
- 第二,一般來說,剛解鎖再去搶鎖的更容易一些,類似于上面的結(jié)果,一直是線程1在搶票
(二)鎖和線程
- 對于上面第二個(gè)問題來說,我們有處理方法,這種方法就是同步,同步可以讓所有的線程按照一定的順序獲取鎖
- 對于其他線程來講,一個(gè)線程要么獲取到了鎖,要么釋放了鎖,當(dāng)前進(jìn)程訪問臨界區(qū)的過程對于其他線程是原子的
在加鎖期間,即解鎖之前,是可以發(fā)生線程切換的,線程切換的時(shí)候是拿著鎖走的,被鎖起來的內(nèi)容其他線程也是訪問不到臨界區(qū)的的,在該線程再次切換回來的時(shí)候,恢復(fù)線程上下文繼續(xù)訪問臨界區(qū)代碼
(三)鎖的特點(diǎn)
加鎖的本質(zhì)就是用時(shí)間來換取安全,我們知道在加鎖后,臨界區(qū)的代碼只能由一個(gè)線程執(zhí)行,如果是并發(fā)執(zhí)行,至少時(shí)間要縮短5倍,但是鎖給我們消除了安全隱患,即可能出現(xiàn)的++
、--
的隱患
加鎖的表現(xiàn)就是線程對于臨界區(qū)代碼串行執(zhí)行,一條線從上到下
我們加鎖的原則就是盡量保證臨界區(qū)的代碼要少一些,可以使單線程執(zhí)行的代碼量更小,多線程綜合處理的代碼量更大,提高效率
鎖的本身是共享資源,所以加鎖和解鎖本身就被設(shè)計(jì)成為了原子性操作(加鎖和解鎖通過硬件提供的原子指令,結(jié)合操作系統(tǒng)內(nèi)核態(tài)的底層同步原語支持以及庫層面的合理封裝,來確保操作的原子性),這樣可以確保在多線程環(huán)境下對共享資源加鎖和解鎖操作的完整性與一致性,避免因多線程并發(fā)干擾導(dǎo)致鎖狀態(tài)異常,進(jìn)而保障線程安全和數(shù)據(jù)的正確性
2、鎖的原理
下面來看一下加鎖解鎖對應(yīng)的匯編指令,我們說,一條匯編指令就是原子性的
首先al寄存器中的數(shù)字為0時(shí),代表鎖已被拿走,為非零(一般為1)時(shí),代表鎖當(dāng)前空閑,可以上鎖
加鎖機(jī)制:
- movb $0, %al:將值 0 移動(dòng)到 AL 寄存器
- xchgb %al, mutex:這是一個(gè)原子交換指令,將 AL 寄存器中的值(即 0)與 mutex 變量的值交換
- if (al寄存器的內(nèi)容 > 0):檢查 AL 寄存器中的內(nèi)容(此時(shí)它保存的是原來 mutex 的值),如果值大于 0,說明互斥鎖之前沒有被鎖定,鎖定成功,返回 0
- else:如果 AL 中的值是 0,說明互斥鎖已經(jīng)被鎖定,程序會(huì)等待
- goto lock:程序跳轉(zhuǎn)回 lock 標(biāo)簽,重新嘗試獲取鎖
解鎖機(jī)制:
- movb $1, mutex:將值 1 移動(dòng)到 mutex
- xchgb %al, mutex:通過交換 AL 中的值和 mutex,實(shí)現(xiàn)解鎖
- return 0:解鎖后,函數(shù)返回
四、鎖的封裝
1、LockGuard.hpp
#pragma once #include <pthread.h> //簡單的封裝了一下函數(shù),用的時(shí)候方便一些 class Mutex { public: Mutex(pthread_mutex_t *lock) :lock_(lock) {} void Lock() { pthread_mutex_lock(lock_); } void Unlock() { pthread_mutex_unlock(lock_); } private: pthread_mutex_t *lock_; }; class LockGuard { public: LockGuard(pthread_mutex_t *lock) :mutex_(lock) { mutex_.Lock(); // 對象創(chuàng)建的時(shí)候加鎖 } ~LockGuard() { mutex_.Unlock(); // 對象銷毀的時(shí)候解鎖 } private: Mutex mutex_; };
#include <iostream> #include <pthread.h> #include <vector> #include <cstdio> #include <unistd.h> #include "LockGuard.hpp" using namespace std; #define NUM 4 int tickets = 500; //全局變量定義鎖 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; class ThreadInfo { public: ThreadInfo(const string &threadname) : threadname_(threadname) public: string threadname_; }; void *GrabTickets(void *args) { ThreadInfo *ti = static_cast<ThreadInfo *>(args); string name(ti->threadname_); while (true) { { LockGuard lockguard(&lock); // RAII 風(fēng)格的鎖 if (tickets > 0) { usleep(10000); printf("%s get a ticket: %d\n", name.c_str(), tickets); tickets--; } else { break; } } usleep(13); // 用休眠來模擬搶到票的后續(xù)動(dòng)作 } printf("%s quit...\n", name.c_str()); } int main() { vector<pthread_t> tids; vector<ThreadInfo *> tis; for (int i = 1; i <= NUM; i++) { pthread_t tid; ThreadInfo *ti = new ThreadInfo("Thread-" + to_string(i)); pthread_create(&tid, nullptr, GrabTickets, ti); tids.push_back(tid); tis.push_back(ti); } // 等待所有線程 for (auto tid : tids) { pthread_join(tid, nullptr); } // 釋放資源 for (auto ti : tis) { delete ti; } pthread_mutex_destroy(&lock); return 0; }
這里封裝的鎖是RAII風(fēng)格的鎖,RAII風(fēng)格是一種在 C++ 等編程語言中利用對象的構(gòu)造和析構(gòu)函數(shù)來自動(dòng)管理資源的技術(shù),確保資源在對象創(chuàng)建時(shí)獲取,在對象生命周期結(jié)束時(shí)自動(dòng)釋放,以防止資源泄漏并簡化資源管理
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Debian 9系統(tǒng)下修改默認(rèn)網(wǎng)卡為eth0的方法
這篇文章主要給大家介紹了在Debian 9系統(tǒng)下修改默認(rèn)網(wǎng)卡為eth0的方法,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-06-06解決Centos7下crontab+shell腳本定期自動(dòng)刪除文件問題
小編最近遇到這樣的需求,就是rsync每次同步的數(shù)據(jù)量很多,但是需要保留的數(shù)據(jù)庫bak文件,保留7天就夠了,所以需要自動(dòng)清理文件夾內(nèi)的bak文件。這篇文章主要介紹了解決Centos7下crontab+shell腳本定期自動(dòng)刪除文件問題,需要的朋友可以參考下2018-11-11Linux保姆級配置vscode連接遠(yuǎn)端主機(jī)以及免密配置過程
這篇文章主要介紹了Linux保姆級配置vscode連接遠(yuǎn)端主機(jī)以及免密配置過程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03每天一個(gè)linux命令(30): chown命令詳解
本篇文章主要介紹了linux chown命令。chown將指定文件的擁有者改為指定的用戶或組,感興趣的朋友可以了解一下。2016-11-11