線程封裝與互斥

目錄

線程互斥

進程線程間的互斥相關背景概念

互斥量mutex

互斥量的接口

初始化互斥量有兩種方法:

銷毀互斥量

互斥量加鎖和解鎖

改進售票系統

?互斥量實現原理探究

??互斥量的封裝


線程互斥

進程線程間的互斥相關背景概念

  • 臨界資源:多線程執行流共享的資源就叫做臨界資源
  • 臨界區:每個線程內部,訪問臨界資源的代碼,就叫做臨界區
  • 互斥:任何時刻,互斥保證有且只有一個執行流進入臨界區,訪問臨界資源,通常對臨界資源起保護作用
  • 原子性:不會被任何調度機制打斷的操作,該操作只有兩態,要么完成,要么未完成

互斥量mutex


大部分情況,線程使用的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,變量
歸屬單個線程,其他線程無法獲得這種變量。但有時候,很多變量都需要在線程間共享,這樣的變量稱為共享變量,可以通過數據的共享,完成線程之間的交互。多個線程并發的操作共享變量,會帶來一些問題

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg)
{char *id = (char *)arg;while (1){if (ticket > 0){usleep(1000);//模擬搶票printf("%s sells ticket:%d\n", id, ticket);//搶到票ticket--;}else{break;}}return nullptr;
}
int main()
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route,(void*) "thread 1");pthread_create(&t2, NULL, route,(void*) "thread 2");pthread_create(&t3, NULL, route,(void*) "thread 3");pthread_create(&t4, NULL, route,(void*) "thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}

上述代碼用多線程模擬搶票,但是為什么搶到負數???

1. if 語句判斷條件為真以后,代碼可以并發的切換到其他線程

if判斷只是對票數進行判斷,在cpu內進行邏輯運算,當某個線程剛載入cpu內判斷時間片到了就被替換成下一個線程,這樣的線程不止一個,再次調度回來時才對--ticket,調度多了,連著減,減成了負數
2.usleep 這個模擬漫長業務的過程,在這個漫長的業務過程中,可能有很多個線程會進入該代碼

計算機的運行速度很快,1毫秒內搶票很快,多個線程進入該代碼陷入內核線程又被掛起
3.--ticket 操作本身就不是一個原子操作

在計算機內部,--tecket簡單分為三步驟:操作要把數據從內存加載到cpu,然后cpu進行計算,最后再把結束寫回內存。宏觀上是cpu在進行調度,假如cpu調度時時間片到了,進程阻塞掛起時,把該線程的上下文保存,在下一次調度時拷貝回繼續運行,在此期間還有別的線程在搶票,不斷得對ticket操作,ticket不斷減少,但是上一次還沒有調度完的線程繼續切回來,上一次上下文運行的結果又寫回內存,此時ticket的數值反而又會增大

全局資源沒有被保護,可能會有并發問題,也就是線程安全。要解決以上問題,需要做到三點:

  • 代碼必須要有互斥行為:當代碼進入臨界區執行時,不允許其他線程進入該臨界區。
  • 如果多個線程同時要求執行臨界區的代碼,并且臨界區沒有線程在執行,那么只能允許一個線程進入該臨界區。
  • 如果線程不在臨界區中執行,那么該線程不能阻止其他線程進入臨界區。

要做到這三點,本質上就是需要一把鎖,在內核態返回用戶態時進行檢查。Linux上提供的這把鎖叫互斥量

互斥量的接口

初始化互斥量有兩種方法:

定義鎖
方法1,靜態分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2,動態分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
參數:
mutex:要初始化的互斥量
attr:NULL

銷毀互斥量

釋放鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);


銷毀互斥量需要注意:

  • 使用PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要銷毀,全局的鎖程序結束自動釋放,局部鎖才需要手動釋放
  • 不要銷毀一個已經加鎖的互斥量
  • 已經銷毀的互斥量,要確保后面不會有線程再嘗試加鎖

互斥量加鎖和解鎖

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

調用 pthread_ lock 時,可能會遇到以下情況:

  • 互斥量處于未鎖狀態,該函數會將互斥量鎖定,同時返回成功
  • 發起函數調用時,其他線程已經鎖定互斥量,或者存在其他線程同時申請互斥量,但沒有競爭到互斥量,那么pthread_ lock調?會陷入阻塞(執行流被掛起),等待互斥量解鎖。

申請鎖是為了保護臨界資源的安全,多線程競爭申請鎖,首先多進程就要先看到鎖,鎖本身就是臨界資源,所以申請鎖的過程,必須是原子的,成功就繼續向后訪問臨界資源,申請失敗,阻塞掛起申請執行流,等待下一次調度喚醒,鎖本身的能力本質是將臨界代碼區由并行轉而為串行,加鎖之后在臨界區內部,允許線程切換,即使切出去了鎖還沒釋放,也得等我執行完代碼才會釋放鎖,其他線程拿到鎖,開鎖進入臨界區

改進售票系統

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(td->lockp); // 加鎖if (ticket > 0){usleep(1000);                                             // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;pthread_mutex_unlock(td->lockp); // 用完解鎖}else{pthread_mutex_unlock(td->lockp); // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_mutex_init(&lock, nullptr); // 初始化鎖pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&lock); // 銷毀鎖return 0;
}

全局鎖

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
int ticket = 100;pthread_mutex_t glock =PTHREAD_MUTEX_INITIALIZER;//全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, pthread_mutex_t &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;pthread_mutex_t *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){pthread_mutex_lock(&glock); // 加鎖if (ticket > 0){usleep(1000);                                             // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;pthread_mutex_unlock(&glock); // 用完解鎖}else{pthread_mutex_unlock(&glock);  // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{pthread_mutex_t lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}

?

?互斥量實現原理探究

鎖的原理

1硬件實現:關閉時鐘中斷

2.軟件實現:為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換

經過上面的例子,大家已經意識到單純的 i++ 或者 ++i 都不是原子的,有可能會有數據一致性問題為了實現互斥鎖操作,大多數體系結構都提供了swap或exchange指令,該指令的作用是把寄存器和內存單元的數據相交換,由于只有一條指令,保證了原子性,即使是多處理器平臺,訪問內存的 總線周期也有先后,一個處理器上的交換指令執行時另?個處理器的交換指令只能等待總線周期。 現在我們把lock和unlock的偽代碼改一下

鎖的初始化把mutex初始化為1,申請鎖的時候先把0寫進al,然后交換?al和mutex鎖的數據,此時%al里面是1,mutex是0,申請鎖成功在返回,然后訪問臨界資源,時間片到了,掛起放到調度隊列里了,也沒有關系,鎖還沒有釋放,上下文數據被帶走了。其他的線程都執行move,寄存器和mutex內都是0,交換完還是0,全部進了下一個調度隊列,直到擁有鎖的線程執行完釋放鎖,把1寫回mutex,才能輪到下一個線程申請鎖成功,如果沒有申請成功,鎖被占用了,執行else掛起等待,等擁有鎖的釋放鎖后,才可以執行后面的代碼申請鎖區訪問臨界支援,說白了,誰交換走了1,誰就持有鎖,誰就有優先訪問臨界資源的權力

??互斥量的封裝

1.0

//Mutex.hpp#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};
}
//TextMutex.cc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){td->lockp->Lock(); // 加鎖if (ticket > 0){usleep(1000);                                             // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;td->lockp->Unlock(); // 用完解鎖}else{td->lockp->Unlock();; // 票搶完了,解鎖退出,否則線程一直阻塞break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}

2.0


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <string.h>
#include "Mutex.hpp"
using namespace MutexModule;
int ticket = 100;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 全局初始化
class ThreadDate
{
public:ThreadDate(const std::string &n, Mutex &lock): name(n), lockp(&lock) {}~ThreadDate() {}std::string name;Mutex *lockp;
};
void *route(void *arg)
{ThreadDate *td = static_cast<ThreadDate *>(arg);while (1){LockGuard guard(*td->lockp);if (ticket > 0){usleep(1000);                                             // 模擬搶票printf("%s sells ticket:%d\n", td->name.c_str(), ticket); // 搶到票ticket--;}else{td->lockp->Unlock();break;}}return nullptr;
}
int main()
{Mutex lock;pthread_t t1, t2, t3, t4;ThreadDate *td1 = new ThreadDate("thread 1", lock);pthread_create(&t1, NULL, route, (void *)td1);ThreadDate *td2 = new ThreadDate("thread 2", lock);pthread_create(&t2, NULL, route, (void *)td2);ThreadDate *td3 = new ThreadDate("thread 3", lock);pthread_create(&t3, NULL, route, (void *)td3);ThreadDate *td4 = new ThreadDate("thread 4", lock);pthread_create(&t4, NULL, route, (void *)td4);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}

#include <pthread.h>
#include <iostream>
namespace MutexModule
{class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}

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

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

相關文章

【系統設計】2WTPS生產級數據處理系統設計Review

歡迎來到啾啾的博客&#x1f431;。 記錄學習點滴。分享工作思考和實用技巧&#xff0c;偶爾也分享一些雜談&#x1f4ac;。 有很多很多不足的地方&#xff0c;歡迎評論交流&#xff0c;感謝您的閱讀與評論&#x1f604;。 目錄 反正能用的系統問題分析方案一&#xff1a;簡單多…

歷年北京理工大學保研上機真題

2025北京理工大學保研上機真題 2024北京理工大學保研上機真題 2023北京理工大學保研上機真題 在線測評鏈接&#xff1a;https://pgcode.cn/problem?classification1 判斷身份證校驗位是否正確 題目描述 給定一個身份證號碼&#xff0c;判斷其最后一位校驗位是否正確。 如果…

uni-app學習筆記十--vu3綜合練習

鞏固提升前面學習的知識點,主要涉及下面這方面的運用&#xff1a; 1.v-for運用; 2.v-model雙向綁定&#xff1b; 3.confirm確認事件&#xff1b; 4.click點擊事件&#xff1b; 5.控制按鈕的可點擊和不可點擊&#xff1b; 6.集合刪除和追加元素&#xff0c;獲取集合元素的…

AI時代新詞-AI芯片(AI - Specific Chip)

一、什么是AI芯片&#xff1f; AI芯片&#xff08;AI - Specific Chip&#xff09;是指專為人工智能&#xff08;AI&#xff09;計算任務設計的芯片。與傳統的通用處理器&#xff08;如CPU&#xff09;相比&#xff0c;AI芯片針對深度學習、機器學習等AI應用進行了優化&#x…

華為云Astro前端頁面數據模型選型及綁定IoTDA物聯網數據實施指南

目錄 1. 選擇合適的數據模型類型及推薦理由 自定義模型: 對象模型: 服務模型: 事件模型: 推薦方案: 2. 數據模型之間的邏輯關系說明 服務模型獲取數據: 對象模型承接數據: 前端組件綁定顯示: 數據保存與反饋(可選): (可選)事件模型實時更新: 小結 …

因重新安裝python新版本,pycharm提示找不到python.exe(No Python at“c:\python.exe“)問題解決方法

1、安裝新版本python后提示錯誤如下&#xff1a; 2、打開設置 3、添加Interpreter 4、配置程序的安裝路徑 5、問題完美解決。

一文帶你徹底理清C 語言核心知識 與 面試高頻考點:從棧溢出到指針 全面解析 附帶筆者手寫2.4k行代碼加注釋

引言&#xff1a;C 語言的魅力與挑戰 從操作系統內核到嵌入式系統&#xff0c;從高性能計算到網絡編程&#xff0c;C 語言高效、靈活和貼近硬件的特性&#xff0c;始終占據著不可替代的地位。然而&#xff0c;C 語言的強大也伴隨著較高的學習曲線&#xff0c;尤其是指針、內存管…

GitHub 趨勢日報 (2025年05月22日)

本日報由 TrendForge 系統生成 https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日整體趨勢 Top 10 排名項目名稱項目描述今日獲星總星數語言1microsoft/WSLLinux的Windows子系統? 2524? 26627C2HeyPuter/puter&#x1…

AI智能混剪核心技術解析(一):字幕與標題生成的三大支柱-字幕與標題生成-優雅草卓伊凡

AI智能混剪核心技術解析&#xff08;一&#xff09;&#xff1a;字幕與標題生成的三大支柱-字幕與標題生成-優雅草卓伊凡 引言&#xff1a;文字到畫面的橋梁工程 在AI視頻混剪系統中&#xff0c;字幕與標題生成是連接語言表達與視覺呈現的核心樞紐。優雅草卓伊凡團隊將該功能拆…

如何通過PHPMyadmin對MYSQL數據庫進行管理?

管理MySQL數據庫時&#xff0c;使用PHPMyAdmin是一種常見且方便的方式。PHPMyAdmin是一個基于Web的數據庫管理工具&#xff0c;提供了許多功能&#xff0c;如數據庫創建、表管理、數據查詢、用戶權限設置等。本文將介紹如何通過PHPMyAdmin對MySQL數據庫進行管理&#xff0c;包括…

如何解決大模型返回的JSON數據前后加上```的情況

環境說明 springboot 應用使用dashscope-sdk-java對接阿里百練 deepseek v3模型 問題表現 已經指定了輸出json格式&#xff0c;但指令不明確&#xff0c;輸出JSON格式的寫法如下 注&#xff1a;提示詞一開始是能正常功能的&#xff0c;但過了幾天就出現了異常&#xff0c;原…

uniapp實現H5、APP、微信小程序播放.m3u8監控視頻

目錄 1.APP播放.m3u8監控視頻 2.H5播放.m3u8監控視頻 3.微信小程序播放.m3u8監控視頻 最近在寫一個uniapp實現h5、app、微信小程序兼容三端的播放監控視頻功能&#xff0c;我原本以為一套代碼多處運行&#xff0c;但事實并非如此&#xff0c;h5可以運行&#xff0c;微信小程…

螢石云實際視頻實時接入(生產環境)

螢石云視頻接入 本示例可用于實際接入螢石云開放平臺視頻&#xff0c;同時支持音頻輸入和輸出。 實際優化內容 1.動態獲取token 2.切換各公司和車間時&#xff0c;自動重新初始化播放器 let EZUIKit null; // 第三方庫引用 let EZUIKitPlayers []; // 播放器實例數組 le…

【Dify平臺】使用Dify API 實現網頁內嵌式AI助手

使用 Dify API 實現網頁內嵌式 AI 助手 一. 引言二. Dify API 概述三. 實現網頁內嵌式 AI 助手的技術架構四. 前端實現五. 后端實現六. 功能擴展與優化七. 測試與部署一. 引言 隨著 AI 技術的不斷發展,越來越多的企業希望將智能助手集成到自己的網頁中,實現用戶自動接待、問…

mysql8配置文件my.ini講解,原汁原味直接拷貝再講解

文章目錄 一、原英文版本&#xff0c;不帶注釋二、由原版逐字翻譯成的中文版&#xff08;行行對應&#xff09;三、最常用的配置 一、原英文版本&#xff0c;不帶注釋 # Other default tuning values # MySQL Server Instance Configuration File # -------------------------…

Go語言中內存釋放 ≠ 資源釋放

// QueryUserFileMetas : 批量獲取用戶文件信息 func QueryUserFileMetas(username string, limit int) ([]UserFile, error) {stmt, err : mydb.DBConn().Prepare("select file_sha1,file_name,file_size,upload_at," "last_update from tbl_user_file where u…

win11+vs2022 安裝opencv 4.11.0圖解教程

1. 下載opencv opencv官網下載地址&#xff1a;Releases - OpenCV 2. 雙擊運行該exe&#xff0c;即可進行安裝&#xff0c;安裝文件夾可自行選擇 安裝后目錄如下&#xff1a; 3. 配置環境變量 使用win鍵搜索環境變量&#xff0c;選中系統變量中的Path&#xff0c;然后點擊編輯…

【Linux】進程 信號的產生

&#x1f33b;個人主頁&#xff1a;路飛雪吖~ &#x1f320;專欄&#xff1a;Linux 目錄 一、掌握Linux信號的基本概念 &#x1f320;前臺進程 VS 后臺進程 &#x1f320; 小貼士&#xff1a; &#x1fa84;?個系統函數 --- signal() &#x1fa84;查看信號 --- man 7 sign…

Python 網絡編程入門

目錄 一、前言 二、網絡通信基礎12&#xff1a;TCP 與 UDP 協議解析 2.1 TCP 協議&#xff1a;可靠的面向連接通信 2.2 UDP 協7議&#xff1a;無連接的快速通信 2.3 Sock12et&#xff1a;網絡通信的基石 三、TCP 編程實15戰&#xff1a;從單工通信到雙向聊天 3.1 TCP 客…

Django壓縮包形式下載文件

通過web將minio上的文件以壓縮包-文件夾-文件的形式下載到本地 import os from bx_mes import settings from io import BytesIO import zipfile from django.http import StreamingHttpResponse class FileRemote(GenericAPIView):def post(self,request):# 壓縮包名folder_n…