LINUX系統編程:多線程互斥

目錄

1.鋪墊

2.線程鎖接口的認識

靜態鎖分配

動態鎖的分配

互斥量的銷毀

互斥量加鎖和解鎖

3.加鎖版搶票

4.互斥的底層實現


1.鋪墊

先提一個小場景,有1000張票,現在有4個進程,這四個進程瘋狂的去搶這1000張票,看看會發生什么呢?

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:
static int tickets;//總共的票數
ticket(std::string &name)
:_name(name)
{}std::string &name()
{return _name;
}int _count = 0;//搶到多少票std::string _name;//線程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{while(true){if(t->tickets > 0){usleep(10000);std::cout<< t->name()<<"tickets-garbbing ticket:"<<t->tickets<<std::endl;t->tickets--;t->_count++;}else{break;}}
}using namespace mythread;//自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket*>> threads;// 創建一批線程std::vector<ticket*> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}//啟動一批線程for(auto &t : threads){t.start();}//等待一批線程for(auto &t : threads){std::cout <<t.name() <<" wait sucess "<<std::endl;t.join();}//查看結果for(auto p : data){std::cout << p->_name<<" get tickets "<<p->_count<<std::endl;sleep(1);}return 0;
}


我們發現這四個線程竟然把票數搶到負數了,代碼中已經判斷if(t->tickets > 0)為什么票數還會減為0呢?

假設當前tickets只剩下1時。

thread0進行判斷,thread0發現票數是大于0的,他就會進入循環,但是這個時候thread0的時間片到了,thread0進入等待隊列。

thread1開始執行,thread1進行判斷,thread1發現票數也是大于0的,進入循環,這個時候hread1的時間片到了,thread1進入等待隊列。

thread2和thread3同樣。

當cpu再次調度到thread0的時候,thread0對thickets--, thickets? = 0.

調度到thread1的時候,thread1對thickets--,tickets = -1.

thread2和thread3同樣。

這也就解釋了,為什票會搶到負數,究其原因就是我們搶票+判斷的操作不是原子的,所以我們要通過互斥鎖把這兩個操作編程"原子"的,這個原子是在線程看來是原子的,不是真正意義上的原子。

也可以理解為把線程并行搶票,變成串行搶票,因為鎖只有一把,一次只能有一個線程搶票

2.線程鎖接口的認識

線程鎖有兩種分配方法,靜態全局鎖和局部鎖

靜態鎖分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

動態鎖的分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)

mutex:是要初始化的互斥量。

attr: nullptr。

互斥量的銷毀

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:1.使用PTHREAT_MUTEX_INITIALIZER初始化的靜態鎖不用銷毀。

? ? ? ? ? ?2.互斥量加鎖了,就不要銷毀了。

? ? ? ? ? ?3.銷毀的互斥量,就不要在加鎖了。

互斥量加鎖和解鎖

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值 : 成功返回 0, 失敗返回錯誤碼。

注意:lock的時候會有兩種情況,一種是lock成功返回0。

??????????另一種是互斥量已經被lock,這時候該線程會阻塞等待。

3.加鎖版搶票

靜態鎖板

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定義一個全局鎖class ticket
{
public:static int tickets; // 總共的票數ticket(std::string &name): _name(name){}std::string &name(){return _name;}int _count = 0;    // 搶到多少票std::string _name; // 線程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在這里上鎖,在這里上鎖,一個線程就把票搶完了while (true){pthread_mutex_lock(&mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}
}using namespace mythread; // 自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 創建一批線程std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}// 啟動一批線程for (auto &t : threads){t.start();}// 等待一批線程for (auto &t : threads){sleep(1);std::cout << t.name() << " wait sucess " << std::endl;t.join();}// 查看結果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}

動態鎖板

在主函數定義一個局部鎖

然后在ticket類中,增加一個互斥量,這個互斥量是要加引用的,為了所有的線程都能看見同一個鎖。

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:static int tickets; // 總共的票數ticket(std::string &name, pthread_mutex_t &mutex): _name(name), _mutex(mutex){pthread_mutex_init(&mutex, nullptr);}std::string &name(){return _name;}int _count = 0;    // 搶到多少票std::string _name; // 線程的名字pthread_mutex_t &_mutex;//讓所有的線程看到同一個鎖
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在這里上鎖,在這里上鎖,一個線程就把票搶完了while (true){pthread_mutex_lock(&t->_mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&t->_mutex);}else{pthread_mutex_unlock(&t->_mutex);break;}}
}using namespace mythread; // 自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 創建一批線程pthread_mutex_t mutex;std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name, mutex);data.push_back(t);threads.emplace_back(handler, t, name);}// 啟動一批線程for (auto &t : threads){t.start();}// 等待一批線程for (auto &t : threads){sleep(1);t.join();std::cout << t.name() << " wait sucess " << std::endl;}// 查看結果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}

運行結果

票是不會搶到負數了,但是出現了個問題。

為什么有的線程一個票也沒搶到?

這個是因為不同的線程競爭能力不同,競爭能力強的就可以一直搶到鎖,而競爭能力不強的就只能等待。

這個需要是用條件變量解決,下次介紹。

4.互斥的底層實現

互斥的底層是依賴swap 和exchange這兩條指令的,這兩條指令是原子的。

正常交換兩個變量都需要,定義一個臨時變量。

但是swap 和exchange這兩條指令不用,可以直接交換,cpu寄存和內存的內容進行交換。

將lock和unlock的過程轉化為偽代碼(粗略只為了解原理)。

假設內存中存在一個mutex鎖,mutex = 1時是解鎖狀態,mutex = 0是上鎖狀態?

我們發現1只有一個,哪個線程拿到1,哪個線程能繼續執行代碼,否則就要掛起等待。

重要的話

放在內存中的數據是所有線程共享的,但是一旦被加載到cpu中,就變成cpu的上下文數據,變成了線程私有的數據

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

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

相關文章

新書速覽|Adobe Firefly:螢火蟲:AI繪畫快速創意設計

《Adobe Firefly&#xff1a;螢火蟲&#xff1a;AI繪畫快速創意設計》 本書內容 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;浪潮的席卷已經變成不可阻擋的趨勢&#xff0c;伴隨著這種變化&#xff0c;在圖形設計、圖像制作、繪畫領域也相應發生了…

什么是接口測試,我們如何實現接口測試?

1. 什么是接口測試 顧名思義&#xff0c;接口測試是對系統或組件之間的接口進行測試&#xff0c;主要是校驗數據的交換&#xff0c;傳遞和控制管理過程&#xff0c;以及相互邏輯依賴關系。其中接口協議分為HTTP,WebService,Dubbo,Thrift,Socket等類型&#xff0c;測試類型又主…

NewspaceGPT帶你玩系列之SQL專家(強烈推薦)

目錄 注冊一個賬號&#xff0c;用qq郵箱&#xff0c;然后登錄選一個可用的Plus&#xff0c;不要選3.5探索GPT今天的主角是SQL Expert&#xff08;SQL 專家&#xff09;問題1&#xff1a;答1. 索引原因&#xff1a;優化措施&#xff1a;示例&#xff1a; 2. 查詢設計原因&#x…

一個利用WebBrowser(古董)控件實現網頁爬蟲的代碼片段

使用WebBrowser控件進行網頁爬蟲的一個基本方式并不是最常見的方法&#xff0c;因為WebBrowser控件主要是為了提供一個嵌入式的瀏覽器界面&#xff0c;而不是為了網頁抓取。然而&#xff0c;你仍然可以通過監聽WebBrowser控件的DocumentCompleted事件來獲取網頁的內容。 以下是…

ros中teleop_twist_keyboard安裝使用

目錄 1.安裝 2.使用 3.說明 1.安裝 sudo apt-get install ros-noetic-teleop-twist-keyboard 其中noetic替換成你自己的ros版本 2.使用 roscore #啟動roscore rosrun teleop_twist_keyboard teleop_twist_keyboard.py …

零基礎STM32單片機編程入門(五)FreeRTOS實時操作系統詳解及實戰含源碼視頻

文章目錄 一.概要二.什么是實時操作系統三.FreeRTOS的特性四.FreeRTOS的任務詳解1.任務函數定義2.任務的創建3.任務的調度原理 五.CubeMX配置一個FreeRTOS例程1.硬件準備2.創建工程3.調試FreeRTOS任務調度 六.CubeMX工程源代碼下載七.講解視頻鏈接地址八.小結 一.概要 FreeRTO…

[SwiftUI 開發] 嵌套的ObservedObject中的更改不會更新UI

1. 發生問題的demo 業務邏輯代碼 class Address: ObservableObject {Published var street "123 Apple Street"Published var city "Cupertino" }class User: ObservableObject {Published var name "Tim Cook"Published var address Addr…

解決 Win11 微軟拼音輸入法下 JetBrains IDE Shift+F6 失效的問題

一、使用舊版微軟拼音輸入法 1.在任務欄中輸入法圖標上右鍵&#xff0c;點擊“設置”&#xff0c;或者在系統設置中進入“時間和語言 -> 語言和區域 -> 微軟拼音輸入法”設置項。 2.點擊進入“常規”類別&#xff0c;滾動到頁面底部&#xff0c;找到“兼容性 -> 使用…

nacos漏洞小結

Alibaba Nacos是阿里巴巴推出來的一個新開源項目&#xff0c;是一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。致力于幫助發現、配置和管理微服務。Nacos提供了一組簡單易用的特性集&#xff0c;可以快速實現動態服務發現、服務配置、服務元數據及流量管理…

我的創作紀念日 第四年 我在人間遭罪,也在人間享樂

回顧 一晃四年過去了&#xff0c;從畢業到現在依舊沒有后悔自己當初的選擇是工作而不是繼續讀研。 讀研雖然可以給我更高的起點&#xff0c;但破碎的底層建筑和生活壓力讓我沒的選擇&#xff0c;畢竟只是一介凡人&#xff0c;而且還是底層出身&#xff0c;環境差&#xff0c;觀…

64、哥倫比亞大學:CU-Net-目前腦腫瘤分割的最先進模型

本文已被接受發表在2024年IEEE MLISE會議上&#xff08;c&#xff09;2024 IEEE。準確地將腦腫瘤從MRI掃描中分割出來對于制定有效的治療方案和改善患者預后至關重要。本研究引入了一種新的哥倫比亞大學網絡&#xff08;CU-Net&#xff09;架構實現&#xff0c;用于使用BraTS 2…

收銀系統源碼-千呼新零售2.0【移動管理端】

千呼新零售2.0系統是零售行業連鎖店一體化收銀系統&#xff0c;包括線下收銀線上商城連鎖店管理ERP管理商品管理供應商管理會員營銷等功能為一體&#xff0c;線上線下數據全部打通。 適用于商超、便利店、水果、生鮮、母嬰、服裝、零食、百貨、寵物等連鎖店使用。 詳細介紹請…

如何循環遍歷循環中的剩余元素

1、問題背景 給定一段文本&#xff0c;文本中包含多條錯誤信息&#xff0c;每條錯誤信息包含行號、錯誤路徑和錯誤信息。需要從文本中提取出這些錯誤信息&#xff0c;并以特定的格式輸出。 line, Error 12, This is the Error line, Error 34, Another Error line, Error …

【Linux】線程周邊002之線程安全

&#x1f440;樊梓慕&#xff1a;個人主頁 &#x1f3a5;個人專欄&#xff1a;《C語言》《數據結構》《藍橋杯試題》《LeetCode刷題筆記》《實訓項目》《C》《Linux》《算法》 &#x1f31d;每一個不曾起舞的日子&#xff0c;都是對生命的辜負 目錄 前言 1.Linux線程互斥 1…

每日一題——Python實現PAT乙級1050 螺旋矩陣(舉一反三+思想解讀+逐步優化)6千字好文

一個認為一切根源都是“自己不夠強”的INTJ 個人主頁&#xff1a;用哲學編程-CSDN博客專欄&#xff1a;每日一題——舉一反三Python編程學習Python內置函數 Python-3.12.0文檔解讀 目錄 我的寫法 時間復雜度分析 空間復雜度分析 總結 我要更強 代碼解釋 時間復雜度 …

小區服務前臺小程序的設計

管理員賬戶功能包括&#xff1a;系統首頁&#xff0c;個人中心&#xff0c;住戶管理&#xff0c;管理員管理&#xff0c;員工管理&#xff0c;安保管理&#xff0c;安保分配管理&#xff0c;客服聊天管理 微信端賬號功能包括&#xff1a;系統首頁&#xff0c;公告&#xff0c;…

Mongodb集群中的分布式讀寫

學習mongodb&#xff0c;體會mongodb的每一個使用細節&#xff0c;歡迎閱讀威贊的文章。這是威贊發布的第81篇mongodb技術文章&#xff0c;歡迎瀏覽本專欄威贊發布的其他文章。如果您認為我的文章對您有幫助或者解決您的問題&#xff0c;歡迎在文章下面點個贊&#xff0c;或者關…

互聯網摸魚日報(2024-07-01)

互聯網摸魚日報(2024-07-01) 36氪新聞 最前線 | 孚能科技廣州基地投產&#xff0c;年產能30GWh&#xff0c;主推SPS大軟包產品 本周雙碳大事&#xff1a;800億元“風光火儲”大項目來了&#xff1b;光伏巨頭SolarEdge股價驟跌20%&#xff1b;韓國電池廠大火&#xff0c;鋰電安…

目標檢測算法的優缺點

目標檢測算法在計算機視覺領域具有廣泛的應用&#xff0c;其優缺點因算法類型和具體實現而有所不同。以下是對一些主流目標檢測算法優缺點的概述&#xff1a; 1. 傳統目標檢測算法 優點&#xff1a; 模型簡單&#xff1a;傳統目標檢測算法通常基于手工設計的特征和分類器&am…

Java進階學習|Day3.Java集合類(容器),Stream的使用,哈希初接觸

java集合類&#xff08;容器&#xff09; Java中的集合類主要由Collection和Map這兩個接口派生而出&#xff0c;其中Collection接口又派生出三個子接口&#xff0c;分別是Set、List、Queue。所有的Java集合類&#xff0c;都是Set、List、Queue、Map這四個接口的實現類&#xf…