深入了解線程鎖的使用及鎖的本質

文章目錄

  • 線程鎖的本質
    • 局部鎖的使用
  • 鎖的封裝及演示
    • 線程饑餓問題
  • 線程加鎖本質
  • 可重入和線程安全
  • 死鎖問題

根據前面內容的概述, 上述我們已經知道了在linux下關于線程封裝和線程互斥,鎖的相關的概念, 下面就來介紹一下關于線程鎖的一些其他概念.

線程鎖的本質

當這個鎖是全局的或者是靜態屬性時,可以使用PTHREAD_MUTEX_INITIALIZER (initializer 初始化器(初始化列表那樣的東西)),這個宏來進行初始化.

局部鎖的使用

局部的鎖就要使用pthread_mutex_init()創建, pthread_mutex_destroy()來銷毀
在這里插入圖片描述
在這里插入圖片描述

回調函數處:
在這里插入圖片描述

鎖的封裝及演示

這邊引入鎖的封裝, 將線程名稱與鎖進行封裝的一種保護機制(lock guard):.
意義在于: 創建后再程序結束時會自動釋放鎖,方便使用

LockGuard.hpp定義

#pragma once
#include <iostream>
//不定義鎖,默認認為外部會給我們傳入鎖對象
class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock)//包裝加鎖功能可以實現啟動自定義鎖時自定加鎖,然后對應的函數功能結束自動解鎖(利用構造函數和析構函數的性質實現){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock):_mutex(lock)//_mutex是Mutex的對象,該對象調用對應的方法{_mutex.Lock();//調用Mutex類的加鎖方法}~LockGuard (){_mutex.Unlock();}private:Mutex _mutex;
};

Thread.hpp 對pthread線程的封裝實現

#pragma once
#include <iostream>
#include <functional>
#include <pthread.h>
#include <string>using namespace std;
template<class T>
using func_t = function<void(T)>;//std::function 是C++標準庫中的一個模板類,可以包裝任何可調用目標(callable target),比如函數、lambda表達式、函數對象(functor)等。
template<class T>
class Thread
{
public:Thread(const string &name, func_t<T> func, T data): _name(name), _func(func), _data(data), _tid(0), _isrunning(false){}static void *ThreadRoutine(void *args)//子線程入口,接受參數為當前對象的指針{Thread *t = static_cast<Thread*>(args);//轉義為所需要的指針類型,當前的t和this一樣,但是不能與庫內的this進行重名t->_func(t->_data); //當前對象調用參數_func(他是一個function類創建的對象,這個類可以包裝任何內容,這邊包裝函數,_func是這個函數模板創建的對象),接受來自Thread創建時的第三個參數//到這邊是完成對整個類的包裝,模板概念已經結束,具體操作回到main內查看,對應的函數執行結束后,執行exit(0)exit(0);}bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);//創建線程,加載輸出OS給的tid,默認方式創建(不設置分離狀態,棧大小等),子線程入口,傳入參數給子線程if(n == 0){_isrunning = true;return true;}else{return false;}}bool Join(){if(!_isrunning){return true;}int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}else{return false;}}~Thread(){}bool IsRunning(){return _isrunning;}private:pthread_t _tid;string _name;func_t<T> _func;T _data;bool _isrunning;
};

main.cc代碼演示

#include "Thread.hpp"
#include <unistd.h>
#include "LockGuard.hpp"class ThreadData
{
public:ThreadData(string name, pthread_mutex_t *pmutex): _name(name), pmutex(pmutex){}~ThreadData(){}public:string _name;pthread_mutex_t *pmutex;
};
int numsize = 10000;string GetThreadName()
{static int num = 1;return static_cast<string>("Thread-" + to_string(num++));
}
void Print(ThreadData *td)//執行Print方法,參數是來自線程創建函數的第四個參數,這一功能也由線程創建函數實現,功不可沒,十分可秒啊
{//全局內定義的參數進行--操作,驗證線程互斥問題while (true){{//將臨界區進行花括號包裹,代碼更加明顯LockGuard lockguard(td->pmutex);//利用鎖保護功能模塊進行加鎖(啟動鎖)// LockGuard lockguard(&mutex);// pthread_mutex_lock(mutex);if (numsize > 0){usleep(1000);std::cout << td->_name << ", the numsize is: " << numsize << std::endl;--numsize;// pthread_mutex_unlock(mutex);}else{// pthread_mutex_unlock(mutex);break;}}//加鎖, 解鎖功能結束,一個線程訪問臨界區的操作也結束,意味著后續線程可以訪問這個臨界區//在運行結果時會發現,有時候會出現一個線程把所有numsize都分完了,這是因為線程執行多久是由于時間片決定,當在多線程情況下把所有任務(同一份資源)都做完的情況叫做多線程饑餓問題}
}int main()
{pthread_mutex_t mutex; // 創建鎖初始化pthread_mutex_init(&mutex, nullptr);string name1 = GetThreadName();//獲取線程名稱ThreadData *td1 = new ThreadData(name1, &mutex); // 將鎖和線程的名字的信息寫入ThreadData,便于管理Thread<ThreadData *> t1(name1, Print, td1);//為線程創建進行加載對應信息string name2 = GetThreadName();ThreadData *td2 = new ThreadData(name2, &mutex);Thread<ThreadData *> t2(name2, Print, td2);string name3 = GetThreadName();ThreadData *td3 = new ThreadData(name3, &mutex);Thread<ThreadData *> t3(name3, Print, td3);string name4 = GetThreadName();ThreadData *td4 = new ThreadData(name4, &mutex);Thread<ThreadData *> t4(name4, Print, td4);string name5 = GetThreadName();ThreadData *td5 = new ThreadData(name5, &mutex);Thread<ThreadData *> t5(name5, Print, td5);t1.Start();//線程啟動t2.Start();t3.Start();t4.Start();t5.Start();t1.Join();//線程等待t2.Join();t3.Join();t4.Join();t5.Join();pthread_mutex_destroy(&mutex); // 消除鎖return 0;
}

基于上篇文章定義對main內的一些修改:
在這里插入圖片描述

線程饑餓問題

再多線程創建后
在運行結果時會發現,有時候會出現一個線程把所有numsize都分完了,這是因為線程執行多久是由于時間片決定,當在多線程情況下把所有任務(同一份資源)都做完的情況叫做多線程饑餓問題.

要解決饑餓問題要讓線程在執行時,預備一定的順序性–這就是線程同步(下章見曉)

線程加鎖本質

原子性問題在軟硬件層面的體現
軟件方面

線程能被調度是因為OS以一種非常快的方式來受理時鐘中斷,這時就會執行調度進程

硬件方面

把中斷關掉,這時只執行進程,OS不會繼續執行,這時不會進行調度

大部分的體系結構(像X86,AMD芯片中)會提供swap和exchange匯編級的指令,作用是把寄存器的內容和內存單元的內容進行數據交換

1.exchange eax mem_addr //將eax 和 mem_addr的內容進行交換
直接進行交換,這一個操作是原子性的
2.什么是一把鎖?在代碼中是創建一個變量,首先把他想象成一個變量struct {int num = 1;}
利用偽代碼進行理解:在這里插入圖片描述
在這里插入圖片描述

關于加鎖的原則: 誰加鎖,誰解鎖.

可重入和線程安全

可重入VS線程安全:

可重入還是不可重入描述的是函數的問題,跟線程無關,他描述的是函數的特點,無褒貶之分,函數大部分都是不可重入

線程安全,:
多個線程并發同一段代碼時,不會出現不同的結果,常見對全局變量或靜態變量進行操作,并且沒有鎖保護
的情況下,會出現該問題,它描述的是線程的特征

eg:線程訪問不可重入函數是線程不安全的情況之一

線程安全的操作:

對于一個全局的變量,在開始改變完他的值之后在退出這個函數之前將值恢復成開始的值,這樣來變相的達到線程安全的操作,這只是其中一個例子

可重入與線程安全是二義性

函數可重入意味著當線程進入這個函數是線程安全的
反之,當這個函數不可重入,那么就是線程不安全的

死鎖問題

問題解釋: 處于一組進程中的各個線程不會釋放資源,但因為相互申請被其他進程所占據不會釋放資源而處于一種永久等待的狀態(多個執行流在一段時間內因為相互牽制不會向后推進)

死鎖產生的四個必要條件:

互斥條件:一個資源只能被一個執行流使用(產生死鎖的根本原因)
請求與保持條件:一個執行流因請求資源而阻塞時,對已獲得的資源保持不放(把自己的鎖拿的緊緊地,還伸手向別人要鎖)
不剝奪條件:一個執行流已獲得的資源,在未使用之前,不能被強行剝奪(鎖2不能解鎖1)
循環等待條件:若干執行流之間形成一種頭尾相接的循環等待資源關系(互相申請對方的的鎖的問題,形成了申請的循環)

當上述四個條件都成立才會產生死鎖

如何避免死鎖呢?

避免死鎖:不用鎖
但是為了保護共享資源, 提出來的使用鎖
核心原理:
1.破壞4個必要條件中的一個或者多個
2.建議, 按照同樣的次序進行申請鎖的操作(加鎖循序盡量保持一致)
盡量把鎖的資源,按照申請的資源一次給申請線程了,這樣不易出現錯誤(目前用不到)
3.避免鎖未被釋放的場景發生
4.資源一次性分配

注:

一個線程也能實現死鎖:
比如:不下心把解鎖寫成了加鎖,這個時候就會出錯
這個時候是自己阻塞自己

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/42842.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/42842.shtml
英文地址,請注明出處:http://en.pswp.cn/web/42842.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Codeforces Round #956 (Div. 2) and ByteRace 2024

A. Array Divisibility 思路: 找出特例,發現輸出 1~&#x1d45b; 符合題意。直接輸出1~n即可. 代碼: #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000005 ll dp[N], w[N], v[N], h[N]; ll dis[1005][1005]; ll a, b, c, n, m, t;…

iOS 開發技巧 - 使用本地 json 文件

前言 使用本地 json 文件的場景&#xff0c;在我們開發功能的階段&#xff0c;服務端接口字段定義好了后&#xff0c;有些接口響應很慢&#xff0c;請求到響應可能要 幾十秒甚至一分鐘&#xff0c;我們需要頻繁調用接口來調試功能&#xff1b;還有就是調用一些我們需要付費的三…

Ubuntu20.04下修改samba用戶密碼

Ubuntu20.04下修改samba用戶密碼 在Ubuntu系統中&#xff0c;修改samba密碼通常涉及到兩個方面&#xff1a;更改samba用戶的密碼和重置samba服務的密碼數據庫。以下是如何進行操作的步驟&#xff1a; 1、更改samba用戶密碼&#xff1a; 打開終端&#xff0c;使用以下命令更改…

vue打包terser壓縮去除控制臺打印和斷點

情況一&#xff1a; 1、vue-cli搭建 代碼壓縮工具terser在vue-cli里面是自動支持的&#xff0c;所以直接在vue.config.js里面加入下面配置&#xff1a; const {defineConfig} require(vue/cli-service) module.exportsdefineConfig({transpileDependencies:true,terser:{te…

看影視學英語(假如第一季第一集)

in the hour也代表一小時嗎&#xff1f;等同于in an hour&#xff1f;

activemq-CVE-2022-41678

Apache ActiveMQ Jolokia 后臺遠程代碼執行漏洞 Apache ActiveMQ在5.16.5&#xff0c;5.17.3版本及以前&#xff0c;后臺Jolokia存在一處任意文件寫入導致的遠程代碼執行漏洞。 啟動環境 admin/admin 方法一&#xff1a;利用poc 這個方法受到ActiveMQ版本的限制&#xff0c;因…

Linux 創建新虛擬機的全過程圖解

一、創建新虛擬機 1.選擇自定義 2.直接下一步 3.選擇稍后安裝 4.設置虛擬機名和安裝位置 5.配置處理器&#xff08;處理器數量&#xff1a;4、每個處理器的內核&#xff1a;2&#xff09; 6. 內存選擇 7.網絡類型 8. IO控制器類型-默認推薦 9.磁盤類型-默認推薦 10.選擇虛擬磁…

JS代碼動態打印404頁面源碼

JS代碼動態打印404頁面源碼&#xff0c;適合做網站錯誤頁&#xff0c;具有js動態打印效果&#xff0c;喜歡的朋友可以拿去 源碼由HTMLCSSJS組成&#xff0c;記事本打開源碼文件可以進行內容文字之類的修改&#xff0c;雙擊html文件可以本地運行效果&#xff0c;也可以上傳到服務…

Redis 7.x 系列【22】主從復制配置項

有道無術&#xff0c;術尚可求&#xff0c;有術無道&#xff0c;止于術。 本系列Redis 版本 7.2.5 源碼地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目錄 1. 前言2. 配置說明2.1 replicaof2.2 masterauth2.3 masteruser2.4 replica-serve-sta…

Linux udp編程

我最近開了幾個專欄&#xff0c;誠信互三&#xff01; > |||《算法專欄》&#xff1a;&#xff1a;刷題教程來自網站《代碼隨想錄》。||| > |||《C專欄》&#xff1a;&#xff1a;記錄我學習C的經歷&#xff0c;看完你一定會有收獲。||| > |||《Linux專欄》&#xff1…

Go中gin框架的*gin.Context參數常見實用方法

梗概&#xff1a; *gin.Context是處理HTTP請求的核心。ctx代表"context"&#xff08;上下文&#xff09;&#xff0c;它包含了處理請求所需的所有信息和方法&#xff0c;例如請求數據、響應構建器、路由參數等。 基本的格式&#xff1a; func SomeHandler(ctx *gi…

空間計量模型及 Stata 具體操作步驟

目錄 一、引言 二、空間計量模型理論原理 空間自回歸模型&#xff08;SAR&#xff09;&#xff1a; 空間誤差模型&#xff08;SEM&#xff09;&#xff1a;&#xff0c; 空間杜賓模型&#xff08;SDM&#xff09;&#xff1a; 三、實證模型構建 四、數據準備 五、Stata …

14-56 劍和詩人30 - IaC、PaC 和 OaC 在云成功中的作用

介紹 隨著各大企業在 2024 年加速采用云計算&#xff0c;基礎設施即代碼 (IaC)、策略即代碼 (PaC) 和優化即代碼 (OaC) 已成為成功實現云遷移、IT 現代化和業務轉型的關鍵功能。 讓我在云計劃的背景下全面了解這些代碼功能的當前狀態。我們將研究現代云基礎設施趨勢、IaC、Pa…

【電路筆記】-C類放大器

C類放大器 文章目錄 C類放大器1、概述2、C類放大介紹3、C類放大器的功能4、C 類放大器的效率5、C類放大器的應用:倍頻器6、總結1、概述 盡管存在差異,但我們在之前有關 A 類、B 類和 AB 類放大器的文章中已經看到,這三類放大器是線性或部分線性的,因為它們在放大過程中再現…

Collection 和 Collections 的區別與用法

Collection 和 Collections 的區別與用法 1、Collection 接口1.1 主要特點1.2 常見方法 2、 Collections 工具類2.1 主要特點2.2 常見方法 3、示例代碼3.1 使用 Collection 接口3.2 使用 Collections 工具類 4、總結 &#x1f496;The Begin&#x1f496;點點關注&#xff0c;收…

STM32學習歷程(day6)

EXTI外部中斷使用教程 首先先看下EXTI的框圖 看這個框圖就能知道要先初始化GPIO外設 那么和前面一樣 1、先RCC使能時鐘 2、配置GPIO 選擇端口為輸入模式&#xff0c; 3、配置AFIO&#xff0c;選擇我們用的GPIO連接到后面的EXTI 4、配置EXTI&#xff0c;選擇邊沿觸發方式…

LVS實驗

LVS實驗 nginx1 RS1 192.168.11.137 nginx2 RS2 192.168.11.138 test4 調度器 ens33 192.168.11.135 ens36 12.0.0.1 test2 客戶端 12.0.0.10 一、test4 配置兩張網卡地址信息 [roottest4 network-scripts]# cat ifcfg-ens33 TYPEEthernet BOOTPROTOstatic DEFROUTEyes DEVIC…

詳解平面DP(上)

前言 其實平面DP和正常的dp沒有什么本質上的區別&#xff0c;只不過是在二維的面上進行DP&#xff0c;而且&#xff0c;客觀的說&#xff0c;其實和遞推沒有什么區別&#xff0c;不要把他想的太難了 講解 本蒻雞思前想后&#xff0c;好像關于平面DP的理論知識好像沒有什么&a…

前后端分離系統

前后端分離是一種現代軟件架構模式&#xff0c;特別適用于Web應用開發&#xff0c;它強調將用戶界面&#xff08;前端&#xff09;與服務器端應用邏輯&#xff08;后端&#xff09;相分離。兩者通過API接口進行數據交互。這種架構模式的主要優勢在于提高開發效率、維護性和可擴…

Git命令常規操作

目錄 常用操作示意圖 文件的狀態變化周期 1. 創建文件 2. 修改原有文件 3. 刪除原有文件 沒有添加到暫存區的數據直接 rm 刪除即可&#xff1a; 對于添加到暫存區的數據 文件或目錄&#xff1a; 4. 重命名暫存區數據 5. 查看歷史記錄 6. 還原歷史數據 恢復過程的原…