Linux線程互斥之線程加鎖的使用詳解
一、鎖的定義
線程加鎖是在多線程編程環(huán)境中,為了確保在同一時(shí)刻只有一個(gè)線程能夠訪問(wèn)特定的共享資源或執(zhí)行特定的代碼段,而采取的一種同步手段,通過(guò)在需要保護(hù)的資源或代碼段前獲取鎖,在訪問(wèn)完成后釋放鎖,來(lái)實(shí)現(xiàn)對(duì)共享資源的互斥訪問(wèn)
二、庫(kù)函數(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線程庫(kù)中定義的互斥鎖類型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); // 用休眠來(lái)模擬搶到票的后續(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)好多線程搶到一張票或者搶到不存在的票的問(wèn)題了
三、深入理解鎖
1、解讀鎖的機(jī)制
(一)先入為主原則
我們將上方代碼中表示搶到票后續(xù)動(dòng)作的休眠代碼注釋掉再次執(zhí)行程序我們會(huì)發(fā)現(xiàn),都是線程1搶的票,多次執(zhí)行代碼之后發(fā)現(xiàn)這是概率性問(wèn)題,但是在搶票的時(shí)候,有一段時(shí)間的票都是一個(gè)線程搶到的,我們預(yù)想的應(yīng)該是幾乎平均分配的樣子

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

首先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í)它保存的是原來(lái) mutex 的值),如果值大于 0,說(shuō)明互斥鎖之前沒(méi)有被鎖定,鎖定成功,返回 0
- else:如果 AL 中的值是 0,說(shuō)明互斥鎖已經(jīng)被鎖定,程序會(huì)等待
- goto lock:程序跳轉(zhuǎn)回 lock 標(biāo)簽,重新嘗試獲取鎖
解鎖機(jī)制:
- movb $1, mutex:將值 1 移動(dòng)到 mutex
- xchgb %al, mutex:通過(guò)交換 AL 中的值和 mutex,實(shí)現(xiàn)解鎖
- return 0:解鎖后,函數(shù)返回
四、鎖的封裝
1、LockGuard.hpp
#pragma once
#include <pthread.h>
//簡(jiǎn)單的封裝了一下函數(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(); // 對(duì)象創(chuàng)建的時(shí)候加鎖
}
~LockGuard()
{
mutex_.Unlock(); // 對(duì)象銷毀的時(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); // 用休眠來(lái)模擬搶到票的后續(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++ 等編程語(yǔ)言中利用對(duì)象的構(gòu)造和析構(gòu)函數(shù)來(lái)自動(dòng)管理資源的技術(shù),確保資源在對(duì)象創(chuàng)建時(shí)獲取,在對(duì)象生命周期結(jié)束時(shí)自動(dòng)釋放,以防止資源泄漏并簡(jiǎn)化資源管理
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
linux服務(wù)器下通過(guò)iptables+Denyhost抵御暴力破解的配置方法
這篇文章主要介紹了linux服務(wù)器下通過(guò)iptables + Denyhost抵御暴力破解的方法,需要的朋友可以參考下2014-11-11
CentOS 7 在vmware中的網(wǎng)絡(luò)設(shè)置詳細(xì)介紹
這篇文章主要介紹了CentOS 7 在vmware中的網(wǎng)絡(luò)設(shè)置詳細(xì)介紹的相關(guān)資料,這里對(duì)主要網(wǎng)絡(luò)問(wèn)題進(jìn)行了一一介紹及如何解決,需要的朋友可以參考下2016-11-11
linux設(shè)置tomcat開機(jī)啟動(dòng)方式
本文介紹了在Linux系統(tǒng)中配置Tomcat開機(jī)自啟動(dòng)的方法,包括創(chuàng)建systemd服務(wù)文件、重新加載系統(tǒng)管理器配置、啟用服務(wù)等步驟,并說(shuō)明了如何指定JDK和配置日志保留期限2026-03-03
Linux-Redhat系統(tǒng)啟動(dòng)讀取文件方式
文章詳細(xì)描述了RHEL系統(tǒng)從啟動(dòng)到登錄的整個(gè)過(guò)程,包括讀取關(guān)鍵配置文件、加載內(nèi)核、掛載文件系統(tǒng)、啟動(dòng)初始化系統(tǒng)以及進(jìn)入用戶界面的步驟2026-02-02
使用CDN之后APACHE日志記錄中IP地址不正確的解決方案
這篇文章主要介紹了使用CDN之后APACHE日志記錄中IP地址不正確的解決方案,需要的朋友可以參考下2014-12-12
Linux中使用Cron定時(shí)執(zhí)行SQL任務(wù)的實(shí)現(xiàn)步驟
在Linux系統(tǒng)中,計(jì)劃任務(wù)(Cron)是一種強(qiáng)大的工具,可以自動(dòng)執(zhí)行預(yù)定的任務(wù),它非常適合定期運(yùn)行腳本、備份數(shù)據(jù)、清理臨時(shí)文件等一系列重復(fù)性任務(wù),本文給大家介紹了如何在Linux中使用Cron定時(shí)執(zhí)行SQL任務(wù),需要的朋友可以參考下2024-11-11
Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因
本篇文章主要介紹了Linux中crontab定時(shí)任務(wù)不執(zhí)行的原因,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03

