Linux線程互斥之線程加鎖的使用詳解
一、鎖的定義
線程加鎖是在多線程編程環(huán)境中,為了確保在同一時刻只有一個線程能夠訪問特定的共享資源或執(zhí)行特定的代碼段,而采取的一種同步手段,通過在需要保護的資源或代碼段前獲取鎖,在訪問完成后釋放鎖,來實現對共享資源的互斥訪問
二、庫函數
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:包含互斥鎖的屬性,設置為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;
//定義一個全局鎖就可以不需要初始化和銷毀鎖的函數了
//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;
}

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

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

首先al寄存器中的數字為0時,代表鎖已被拿走,為非零(一般為1)時,代表鎖當前空閑,可以上鎖
加鎖機制:
- movb $0, %al:將值 0 移動到 AL 寄存器
- xchgb %al, mutex:這是一個原子交換指令,將 AL 寄存器中的值(即 0)與 mutex 變量的值交換
- if (al寄存器的內容 > 0):檢查 AL 寄存器中的內容(此時它保存的是原來 mutex 的值),如果值大于 0,說明互斥鎖之前沒有被鎖定,鎖定成功,返回 0
- else:如果 AL 中的值是 0,說明互斥鎖已經被鎖定,程序會等待
- goto lock:程序跳轉回 lock 標簽,重新嘗試獲取鎖
解鎖機制:
- movb $1, mutex:將值 1 移動到 mutex
- xchgb %al, mutex:通過交換 AL 中的值和 mutex,實現解鎖
- return 0:解鎖后,函數返回
四、鎖的封裝
1、LockGuard.hpp
#pragma once
#include <pthread.h>
//簡單的封裝了一下函數,用的時候方便一些
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 風格的鎖
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風格的鎖,RAII風格是一種在 C++ 等編程語言中利用對象的構造和析構函數來自動管理資源的技術,確保資源在對象創(chuàng)建時獲取,在對象生命周期結束時自動釋放,以防止資源泄漏并簡化資源管理
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
linux服務器下通過iptables+Denyhost抵御暴力破解的配置方法
這篇文章主要介紹了linux服務器下通過iptables + Denyhost抵御暴力破解的方法,需要的朋友可以參考下2014-11-11
使用CDN之后APACHE日志記錄中IP地址不正確的解決方案
這篇文章主要介紹了使用CDN之后APACHE日志記錄中IP地址不正確的解決方案,需要的朋友可以參考下2014-12-12
Linux中使用Cron定時執(zhí)行SQL任務的實現步驟
在Linux系統(tǒng)中,計劃任務(Cron)是一種強大的工具,可以自動執(zhí)行預定的任務,它非常適合定期運行腳本、備份數據、清理臨時文件等一系列重復性任務,本文給大家介紹了如何在Linux中使用Cron定時執(zhí)行SQL任務,需要的朋友可以參考下2024-11-11

