Linux線程互斥之線程加鎖的使用詳解
一、鎖的定義
線程加鎖是在多線程編程環(huán)境中,為了確保在同一時刻只有一個線程能夠訪問特定的共享資源或執(zhí)行特定的代碼段,而采取的一種同步手段,通過在需要保護的資源或代碼段前獲取鎖,在訪問完成后釋放鎖,來實現(xiàn)對共享資源的互斥訪問
二、庫函數(shù)
1、初始化互斥鎖
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 返回值:成功返回0,失敗返回非零錯誤碼
mutex:表示要初始化的互斥鎖,pthread_mutex_t是POSIX線程庫中定義的互斥鎖類型attr:包含互斥鎖的屬性,設(shè)置為NULL表示使用默認屬性
2、銷毀互斥鎖
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯誤碼
mutex:表示要銷毀的互斥鎖
3、加鎖
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯誤碼
mutex:表示要加鎖的互斥鎖
4、解鎖
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 返回值:成功返回0,失敗返回非零錯誤碼
mutex:表示要解鎖的互斥鎖
5、示例
#include <iostream>
#include <pthread.h>
#include <vector>
#include <cstdio>
#include <unistd.h>
using namespace std;
//定義一個全局鎖就可以不需要初始化和銷毀鎖的函數(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ù)動作
}
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;
}

這樣就不會出現(xiàn)好多線程搶到一張票或者搶到不存在的票的問題了
三、深入理解鎖
1、解讀鎖的機制
(一)先入為主原則
我們將上方代碼中表示搶到票后續(xù)動作的休眠代碼注釋掉再次執(zhí)行程序我們會發(fā)現(xiàn),都是線程1搶的票,多次執(zhí)行代碼之后發(fā)現(xiàn)這是概率性問題,但是在搶票的時候,有一段時間的票都是一個線程搶到的,我們預(yù)想的應(yīng)該是幾乎平均分配的樣子

這說明了幾個問題:
- 第一,線程對于鎖的競爭能力不同,一定有一個首先搶到鎖的線程
- 第二,一般來說,剛解鎖再去搶鎖的更容易一些,類似于上面的結(jié)果,一直是線程1在搶票
(二)鎖和線程
- 對于上面第二個問題來說,我們有處理方法,這種方法就是同步,同步可以讓所有的線程按照一定的順序獲取鎖
- 對于其他線程來講,一個線程要么獲取到了鎖,要么釋放了鎖,當(dāng)前進程訪問臨界區(qū)的過程對于其他線程是原子的
在加鎖期間,即解鎖之前,是可以發(fā)生線程切換的,線程切換的時候是拿著鎖走的,被鎖起來的內(nèi)容其他線程也是訪問不到臨界區(qū)的的,在該線程再次切換回來的時候,恢復(fù)線程上下文繼續(xù)訪問臨界區(qū)代碼
(三)鎖的特點
加鎖的本質(zhì)就是用時間來換取安全,我們知道在加鎖后,臨界區(qū)的代碼只能由一個線程執(zhí)行,如果是并發(fā)執(zhí)行,至少時間要縮短5倍,但是鎖給我們消除了安全隱患,即可能出現(xiàn)的++、--的隱患
加鎖的表現(xiàn)就是線程對于臨界區(qū)代碼串行執(zhí)行,一條線從上到下
我們加鎖的原則就是盡量保證臨界區(qū)的代碼要少一些,可以使單線程執(zhí)行的代碼量更小,多線程綜合處理的代碼量更大,提高效率
鎖的本身是共享資源,所以加鎖和解鎖本身就被設(shè)計成為了原子性操作(加鎖和解鎖通過硬件提供的原子指令,結(jié)合操作系統(tǒng)內(nèi)核態(tài)的底層同步原語支持以及庫層面的合理封裝,來確保操作的原子性),這樣可以確保在多線程環(huán)境下對共享資源加鎖和解鎖操作的完整性與一致性,避免因多線程并發(fā)干擾導(dǎo)致鎖狀態(tài)異常,進而保障線程安全和數(shù)據(jù)的正確性
2、鎖的原理
下面來看一下加鎖解鎖對應(yīng)的匯編指令,我們說,一條匯編指令就是原子性的

首先al寄存器中的數(shù)字為0時,代表鎖已被拿走,為非零(一般為1)時,代表鎖當(dāng)前空閑,可以上鎖
加鎖機制:
- movb $0, %al:將值 0 移動到 AL 寄存器
- xchgb %al, mutex:這是一個原子交換指令,將 AL 寄存器中的值(即 0)與 mutex 變量的值交換
- if (al寄存器的內(nèi)容 > 0):檢查 AL 寄存器中的內(nèi)容(此時它保存的是原來 mutex 的值),如果值大于 0,說明互斥鎖之前沒有被鎖定,鎖定成功,返回 0
- else:如果 AL 中的值是 0,說明互斥鎖已經(jīng)被鎖定,程序會等待
- goto lock:程序跳轉(zhuǎn)回 lock 標(biāo)簽,重新嘗試獲取鎖
解鎖機制:
- movb $1, mutex:將值 1 移動到 mutex
- xchgb %al, mutex:通過交換 AL 中的值和 mutex,實現(xiàn)解鎖
- return 0:解鎖后,函數(shù)返回
四、鎖的封裝
1、LockGuard.hpp
#pragma once
#include <pthread.h>
//簡單的封裝了一下函數(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)建的時候加鎖
}
~LockGuard()
{
mutex_.Unlock(); // 對象銷毀的時候解鎖
}
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ù)動作
}
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ù)來自動管理資源的技術(shù),確保資源在對象創(chuàng)建時獲取,在對象生命周期結(jié)束時自動釋放,以防止資源泄漏并簡化資源管理
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Debian 9系統(tǒng)下修改默認網(wǎng)卡為eth0的方法
這篇文章主要給大家介紹了在Debian 9系統(tǒng)下修改默認網(wǎng)卡為eth0的方法,文中介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-06-06
解決Centos7下crontab+shell腳本定期自動刪除文件問題
小編最近遇到這樣的需求,就是rsync每次同步的數(shù)據(jù)量很多,但是需要保留的數(shù)據(jù)庫bak文件,保留7天就夠了,所以需要自動清理文件夾內(nèi)的bak文件。這篇文章主要介紹了解決Centos7下crontab+shell腳本定期自動刪除文件問題,需要的朋友可以參考下2018-11-11
Linux保姆級配置vscode連接遠端主機以及免密配置過程
這篇文章主要介紹了Linux保姆級配置vscode連接遠端主機以及免密配置過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03

