Linux線程互斥鎖

目錄

🚩看現象,說原因

🚩解決方案

🚩互斥鎖

?🚀關于互斥鎖的理解

🚀關于原子性的理解

🚀如何理解加鎖和解鎖是原子的

🚩對互斥鎖的簡單封裝


引言

大家有任何疑問,可以在評論區留言或者私信我,我一定盡力解答。

今天我們學習Linux線程互斥的話題。Linux同步和互斥是Linux線程學習的延伸。但這部分挺有難度的,請大家做好準備。那我們就正式開始了。

🚩看現象,說原因

我們先上一段代碼:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<cassert>
using namespace std;
int NUM=5;
int ticket=1000;
class pthread
{
public:char buffer[1024];pthread_t id;
};
void *get_ticket(void *args)
{pthread *pth=static_cast<pthread*>(args);while(1){usleep(1234);if(ticket<0){return nullptr;}cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;ticket--;}
}int main()
{vector<pthread*> pthpool;for(int i=0;i<NUM;i++){pthread* new_pth=new pthread();snprintf(new_pth->buffer,sizeof (new_pth->buffer),"thread-%d",i+1);int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);assert(n==0);(void)n;pthpool.push_back(new_pth);}for(int i=0;i<pthpool.size();i++){int m= pthread_join(pthpool[i]->id,nullptr);assert(m==0);(void)m;}return 0;}

這段代碼模擬的是搶票模型,一共有一千張票,我們讓幾個線程同時去搶票。看看有什么不符合實際的情況發生。

還真有不符合實際的情況發生:竟然搶到了負票。臥槽,這是什么情況,我們趕緊分析一下。

首先,在代碼中我們定義了一個全局變量:ticket 。這個變量被所有線程所共享。

對于這種情形,我們直接拉向極端情況:假設此時的票數只有一張了。一個線程進入if內部,但是對票數還沒有進行操作,這時,時間片到了,這個線程被切了下去。緊接著,一個線程就通過if判斷,順利搶到了最后一張票,對票數進行了操作。此時已經無票可搶了。這時,那個被切下來的線程又帶著它的數據開始了搶票。但是在這個線程看來,票數依舊還有最后一張,所以,它又對票數進行了減減操作,得到了負票。

這種情況顯然是不合理的,假如一個電影院有100個座位,結果賣出去102張票,這怎么可以呢?

我們定義的全局變量,在沒有保護的情況下,往往曬不安全的。像上面多個線程在交替執行時造成的數據安全問題,我們稱之為出現了數據不一致問題

這就是個坑啊,必須解決。

🚩解決方案

在提出解決方案之前,我們先回顧幾個概念。

  • 多個執行流進行安全訪問的共享資源,叫做臨界資源
  • 我們把多個執行流中,訪問臨界資源的代碼叫做臨界區,臨界區往往是線程代碼很小的一部分。
  • 想讓多個線程串行訪問共享資源的方式叫做互斥
  • 對一個資源進行訪問的時候,要么不做,要么做完,這種特性叫做原子性。一個對資源進行的操作,如果只有一挑匯編語句完成,那么就是原子的,反之就不是原則的。這是當前我們對原子性的理解,后面還會發生改變。

?我們提出的解決方案就是加鎖。相信大家第一次聽到鎖。對于什么是鎖,如何加鎖,鎖的原理是什么我們都不清楚,別著急,我們在接下來的內容里會進行詳細的詳解。

我們先使用一下鎖,見見豬跑!!

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
#include<cassert>
using namespace std;
int NUM=5;
int ticket=1000;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
class pthread
{
public:char buffer[1024];pthread_t id;
};
void *get_ticket(void *args)
{pthread *pth=static_cast<pthread*>(args);while(1){  pthread_mutex_lock(&mutex);usleep(1234);if(ticket<0){  pthread_mutex_unlock(&mutex);return nullptr;}cout<<pth->buffer<<" is ruuing ticket: "<<ticket<<endl;ticket--;pthread_mutex_unlock(&mutex);}
}int main()
{vector<pthread*> pthpool;for(int i=0;i<NUM;i++){pthread* new_pth=new pthread();snprintf(new_pth->buffer,sizeof (new_pth->buffer),"user-%d",i+1);int n=pthread_create(&(new_pth->id),nullptr,get_ticket,new_pth);assert(n==0);(void)n;pthpool.push_back(new_pth);}for(int i=0;i<pthpool.size();i++){int m= pthread_join(pthpool[i]->id,nullptr);assert(m==0);(void)m;}return 0;}

?

結果顯示搶票的過程非常順利,接下來,我們把重心指向鎖。

🚩互斥鎖

?首先,我們先認識一些鎖的常見接口

// 所有鎖的相關操作函數都在這個頭文件下
//這些函數如果又返回值,操作成功的話,返回0,失敗的話。返回錯誤碼。錯誤原因被設置
#include <pthread.h>
// 鎖的類型,用來創建鎖
pthread_mutex_t
// 對鎖進行初始化,第二個參數一般設位null
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
// 如果這個鎖沒有用了,可以調用該函數對鎖進行銷毀
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 如果創建的鎖是全局變量,可以這樣初始化。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 對特定代碼部分進行上鎖,這部分代碼只能有一次只能有一個執行流進入,被保護的資源叫做臨界資源。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 嘗試上鎖,不一定成功。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 取消鎖。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

剛剛,我們已經使用一種方式實現了加鎖,接下來,我們用另一種方式:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cassert>
using namespace std;
int NUM = 5;
int ticket = 1000;
class Thread_Data
{
public:Thread_Data(string name,pthread_mutex_t* mutex):_name(name),_mutex(mutex){}~Thread_Data(){}
public:string _name;pthread_mutex_t*  _mutex;
};
void *get_ticket(void *args)
{Thread_Data *pth = static_cast<Thread_Data*>(args);while (1){pthread_mutex_lock(pth->_mutex); if (ticket > 0){  usleep(1234); cout << pth->_name << " is ruuing ticket: " << ticket << endl;  ticket--;pthread_mutex_unlock(pth->_mutex); } else{pthread_mutex_unlock(pth->_mutex);break;}}
}int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);vector<pthread_t> tids(NUM); for (int i = 0; i < NUM; i++){char buffer[1024];Thread_Data *td=new Thread_Data(buffer,&mutex);snprintf(buffer, sizeof(buffer), "user-%d", i + 1);int n =pthread_create(&tids[i], nullptr, get_ticket, td);assert(n == 0);(void)n;}for (int i = 0; i < tids.size(); i++){int m = pthread_join(tids[i], nullptr);assert(m == 0);(void)m;}return 0;
}

?

運行一下,發現一直是4號線程在跑,其他線程呢?我也沒讓其他線程退出呀!而且搶票的時間變長了。

  • 加鎖和解鎖是多個線程串行進行的,所以程序允許起來會變得很慢。
  • 鎖只規定互斥訪問,沒有規定誰優先訪問。
  • 鎖就是讓多個線程公平競爭的結果,強者勝出嘛。

?🚀關于互斥鎖的理解

  • 所有的執行流都可以訪問這一把鎖,所以鎖是一個共享資源。
  • 加鎖和解鎖的過程必須是原子的,不會存在中間狀態。要么成功,要么失敗。加鎖的過程必須是安全的。
  • 誰持有鎖,誰進入臨界區。

?如果一個執行流申請鎖成功,繼續向后運行;如果申請失敗的話,這個執行流怎么辦?

這種情況試一試不就知道了。我們依舊使用上面的一份代碼,稍稍做一下修改:

?

所以,當一個執行流申請鎖失敗時,這個執行流會阻塞在這里。


🚀關于原子性的理解

如圖,三個執行流

?

問:如果線程1申請鎖成功,進入臨界資源,正在訪問臨界資源區的時候,其他線程在做什么?

?答:都在阻塞等待,直到持有鎖的線程釋放鎖。

問; 如果線程1申請鎖成功,進入臨界資源,正在訪問臨界資源區的時候,可不可以被切換?

答:絕對是可以的,CPU管你有沒有鎖呢,時間片到了你必須下來。當持有鎖的線程被切下來的時候,

是抱著鎖走的,即使自己被切走了,其他線程依舊無法申請鎖成功,也就無法繼續向后執行。

這就叫作:江湖上沒有我,但依舊有我的傳說。

所以對于其他線程而言,有意義的鎖的狀態,無非兩種:①申請鎖前,②釋放鎖后

?所以,站在其他線程的角度來看待當前持有鎖的過程,就是原子的。

?所以,未來我們在使用鎖的時候,要遵守什么樣的原則呢?

  • 一定要保證代碼的粒度(鎖要保護的代碼的多少i)要非常小。
  • 加鎖是程序員的行為,必須做到要加的話所有的線程必須要加鎖。

🚀如何理解加鎖和解鎖是原子的

?在分析如何實現加鎖和解鎖之前,我們先形成幾個共識:

  • CPU內執行流只有一套,且被所有執行流所共享。
  • CPU內寄存器的內容屬線程所有,是每個執行流的上下文。時間片到達,數據帶走。
  • 在進行加鎖和解鎖的時候,這個線程隨時會因時間片已到而被換下來。

為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換,由于只有一條指令,保證了原子性?。

如圖:

我們假設有線程A,B兩個線程,A想要獲得鎖?

鎖內存儲的數據就是int類型的1。?A線程中有數字0。

①:movb $0,%al:將線程A中的1move到寄存器中。此時,是有可能發生時間片到達的,但是寄存器內的數據屬于線程A,線程A是要帶走的。

②:xchgb %al,mutex:將鎖中的數據和寄存器內的數據進行交換。此時寄存器內的數據變成1,鎖中的數據變為0。這是關鍵的一步,也有可能會發生切換。假設不巧的很,A線程被切下去了,B線程被切上來了。B線程從第一步開始,走到現在,寄存器內的數據應該是0。然后進入判斷體eles進行掛起等待。

③如果在第二步中線程A被切下來,等待一段時間,時間片再次輪到線程A時,A將自己的數據加載到寄存器內進入判斷,然后獲得鎖。

交換的過程由一條匯編構成

交換的本質:共享的數據,交換到線程的上下文中。?

那么。如何完成解鎖的操作呢。解鎖的操作特別簡單,只需一步。

將寄存器內的1歸還給鎖。然后return返回就可以了。

🚩對互斥鎖的簡單封裝

相信大家對互斥鎖都有了充分的了解。接下來,我們就實現一下對互斥鎖的簡單封裝。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cassert>class Mutex
{
public:Mutex(pthread_mutex_t *mutex) : _mutex(mutex){}void unlock(){if (_mutex){pthread_mutex_unlock(_mutex);}}void lock(){if(_mutex){pthread_mutex_lock(_mutex);}}~Mutex(){}public:pthread_mutex_t *_mutex;
};
class Lockguard
{
public:Lockguard(Mutex mutex) : _mutex(mutex){_mutex.lock();}~Lockguard(){_mutex.unlock();}public:Mutex _mutex;
};

這種利用變量出了函數作用域自動銷毀的性質,我們稱之為RAII特性。

到這里,我們本篇的內容也就結束了,我們期待下一期博客相遇。

?

?

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

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

相關文章

【Android面試八股文】如何實現Activity窗口快速變暗

文章目錄 方式一:修改 WindowManager.LayoutParams 的screenBrightness屬性動態調整窗口的亮度方式二:使用 `WindowManager.LayoutParams` 的 `alpha` 屬性結合 `ValueAnimator` 來實現窗口漸變變暗的效果方式三:使用遮罩層在Android中實現Activity窗口快速變暗有幾種方法,…

CCSP自考攻略+經驗總結

備考攻略 備考攻略準備階段通讀階段精度階段總復習階段刷題階段命運審判 寫到最后 備考攻略 趁著對ssp知識點的理解還在&#xff0c;開始ccsp的考證之路&#xff0c;文章結構還是按照cissp備考篇的結構梳理。本次備考和cissp的離職在家備考不同&#xff0c;ccsp是在職利用非工…

如何用亞馬遜合作伙伴網絡快速上線跨境電商

目前跨境電商已成為行業發展主流&#xff0c;如何快速、低成本打造品牌海外獨立站和智能客服營銷中心、構建全鏈路跨境電商體系是出海電商商家都會遇到的難題。亞馬遜云科技憑借與亞馬遜電商平臺易于集成的先天優勢成為首選的電商解決方案平臺。本文介紹了如何用亞馬遜云科技平…

Elasticsearch8.x聚合查詢全面指南:從理論到實戰

聚合查詢的概念 聚合查詢&#xff08;Aggregation Queries&#xff09;是Elasticsearch中用于數據匯總和分析的查詢類型。它不同于普通的查詢&#xff0c;而是用于執行各種聚合操作&#xff0c;如計數、求和、平均值、最小值、最大值、分組等。 聚合查詢的分類 分桶聚合&…

centos7 安裝單機MongoDB

centos7安裝單機 yum 安裝 1、配置yum源 vim /etc/yum.repos.d/mongodb.repo [mongodb-org-7.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/7.0/x86_64/ gpgcheck1 enabled1 gpgkeyhttps://www.mongodb.org/static/pgp…

【監控】3.配置 Grafana 以使用 Prometheus 數據源

1 訪問 Grafana 打開瀏覽器&#xff0c;訪問 http://localhost:3000&#xff08;默認端口&#xff09;。使用默認的用戶名和密碼 admin/admin 登錄。 2 添加 Prometheus 數據源 進入 Grafana 儀表板&#xff0c;點擊左側菜單中的“Configuration” -> “Data Sources”。…

未來已來,如何打造智慧養殖場?

近年來&#xff0c;國家出臺了一系列扶持政策&#xff0c;以促進養殖行業高質量發展&#xff0c;推動行業轉型升級。在國家政策和市場需求的雙重驅動下&#xff0c;養殖行業正迎來前所未有的發展機遇。智慧養殖以其高效、智能和可持續的特點&#xff0c;正逐步取代傳統養殖方式…

6.26.4.1 基于交叉視角變換的未配準醫學圖像多視角分析

1. 介紹 許多醫學成像任務使用來自多個視圖或模式的數據&#xff0c;但很難有效地將這些數據結合起來。雖然多模態圖像通常可以在神經網絡中作為多個輸入通道進行配準和處理&#xff0c;但來自不同視圖的圖像可能難以正確配準(例如&#xff0c;[2])。因此&#xff0c;大多數多視…

電影搜索筆記

1 字幕組 2 磁力搜索 2.1 磁力大全 http://7809.org/cldq.html

吳恩達2022機器學習專項課程C2W3:2.27 選修_數據傾斜

目錄 處理不平衡數據集1.分類需求描述2.計算精確率和召回率 權衡精確率和召喚率1.手動調整閾值2.F1分數 總結 處理不平衡數據集 1.分類需求描述 如果你在處理一個機器學習應用&#xff0c;其中正例和負例的比例&#xff08;用于解決分類問題&#xff09;非常不平衡&#xff0…

多域名微信公眾號獲取授權(前后端分離,后端獲取微信用戶openid)

1.通過后端站點http://b.cn 跳轉至前端鏈接 http://aa.cn/v1/demo/demo public function index(){$identinput(ident);$this->redirect(http://aa.cn/?ident.$ident);}2.前端http://aa.cn 再跳轉到后端 https://c.com(此域名綁定微信公眾號:需備案) onShow() {console.lo…

數據庫怎么同步

數據庫要怎么同步呢&#xff0c;有很多方法&#xff0c;看你用什么數據庫&#xff0c;如果是Sqlserver,你要數據庫同步&#xff0c;那么可以使用自帶的訂閱發布&#xff0c;訂閱發布應該是不錯的方法&#xff0c;但是我上次要配置雙向同步&#xff0c;它的對等發布好像沒部署成…

Ansible-綜合練習-生產案例

斌的招兒 網上教程大多都是官網模板化的教程和文檔&#xff0c;這里小斌用自己實際生產環境使用的例子給大家做一個詳解。涉及到一整套ansible的使用&#xff0c;對于roles的使用&#xff0c;也僅涉及到tasks和files目錄&#xff0c;方便大家快速上手并規范化管理。 0.環境配置…

想關掉一個qwidget是用deleteLater還是用close

在Qt中關閉一個QWidget可以選擇使用close()或者deleteLater()兩種方法&#xff0c;根據具體需求&#xff0c;兩者有不同的適用場景&#xff1a; close()方法&#xff1a; close()會觸發QWidget的closeEvent&#xff0c;也就是說&#xff0c;它會產生一個關閉事件&#xff0c;可…

聚星文社AI工具

聚星文社AI工具是一種基于人工智能技術開發的工具&#xff0c;旨在輔助作者和寫作人員提升創作效率和質量。 點擊下載 該工具可以提供多項功能&#xff0c;包括語法糾錯、智能推薦、文章自動摘要等。 通過使用聚星文社AI工具&#xff0c;用戶可以在寫作過程中得到即時的糾錯建…

memcached服務介紹

memcached 基礎概念安裝使用 基礎概念 Memcached 是一個高性能的分布式內存對象緩存系統&#xff0c;用于減少數據庫負載&#xff0c;加速動態 Web 應用。 Memcached 的基本概念 緩存&#xff1a;Memcached 的核心功能是緩存數據&#xff0c;它將經常訪問的數據存儲在內存中…

ECMAScript6介紹及環境搭建

這實際上說明&#xff0c;對象的解構賦值是下面形式的簡寫。 let { foo: foo, bar: bar } { foo: ‘aaa’, bar: ‘bbb’ }; 也就是說&#xff0c;對象的解構賦值的內部機制&#xff0c;是先找到同名屬性&#xff0c;然后再賦給對應的變量。真正被賦值的是后者&#xff0c;而…

數據結構_緒論

1.數據結構的研究內容 研究數據的特性和數據之間的關系 用計算機解決一個問題的步驟 1.具體問題抽象成數學模型 實質: 分析問題--->提取操作對象--->找出操作對象之間的關系(數據結構)--->用數學語言描述 操作對象對象之間的關系 2.設計算法 3.編程,調試,運行 …

GO語言面試題目,使用3個協程按照順序從1打印到100

GO語言面試題目&#xff0c;使用3個協程按照順序從1打印到100 稍微把題目拓展了下&#xff0c;使用N個協程 打印M個數&#xff0c;應該很好理解&#xff0c;創建一個N個協程的列表&#xff0c;然后每打印一個數&#xff0c;就傳到下一個chan中&#xff0c;一次循環 package m…