探索Linux互斥:線程安全與資源共享

個人主頁:chian-ocean

文章專欄-Linux

前言:

互斥是并發編程中避免競爭條件和保護共享資源的核心技術。通過使用鎖或信號量等機制,能夠確保多線程或多進程環境下對共享資源的安全訪問,避免數據不一致、死鎖等問題。

在這里插入圖片描述

競爭條件

競爭條件(Race Condition)是并發程序設計中的一個問題,指在多個線程或進程并發執行時,由于它們對共享資源的訪問順序不確定,可能導致程序的輸出或行為依賴于執行的順序,從而產生不一致或不可預測的結果。

例如:一個假脫機打印程序。當一個進程需要打印一個文件時,它將文件名放在一個特殊的假脫機目錄**(spoalerdirectory)**下。另一個進程(打印機守護進程)則周期性地檢查是否有文件需要打印,若有就打印并將該文件名從目錄下刪掉)

在這里插入圖片描述

  • 理想情況

    1. 設想假脫機目錄中有許多槽位,編號依次為0,1,2,……,每個槽位存放一個文件名。
    2. 同時假設有兩個共享變量:out,指向下一個要打印的文件:in,指向目錄中下一個空閑槽位。
    3. 可以把這兩個變量保存在一個所有進程都out=4進程Ain=7能訪問的文件中,該文件的長度為兩個字。
    4. 在某一時刻,進程B號至3號槽位空(其中的文件已經打印完畢),4號至6號槽位被占用(其中存有排好隊列的要打印的文件名)。幾乎在同時刻,進程A進程B都決定將一個文件排隊打印,這種情圖兩個進程同時想訪問共享內存
  • 實際情況

    1. 進程A讀到 in 的值為7,將7存在一個局部變量 next_free_slot中。
    2. 此時發生一次時鐘中斷,CPU認為進程A已運行了足夠長的時間,決定切換到進程B。進程B也讀取in,同樣得到值為7,于是將7存在B的局部變量next_free_slot中。
    3. 在這一時刻兩個進程都認為下一個可用槽位是7.進程B現在繼續運行,它將其文件名存在槽位7中并將in的值更新為8。然后它離開,繼續執行其他操作最后進程A接著從上次中斷的地方再次運行。
    4. 它檢查變量 next_free_slot,發現其值為7,于是將打印文作名存人7號槽位,這樣就把進程B存在那里的文件名覆蓋掉。然后它將 next_free_slot加1,得到值為8,就將8存到in中。
    5. 此時,假脫機目錄內部是一致的,所以打印機守護進程發現不了任何錯誤,但進程B卻永遠得不到任何打印輸出。類似這樣的情況,即兩個或多個進程讀寫某些共享數據,而最后的結果取決于進程運行的精確時序,稱為競爭條件(race condition)。

實際搶票問題

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM  10 // 定義線程數量,這里創建 10 個線程
int ticket = 1000; // 票數從 1000 開始// 線程執行的函數
void* mythread(void* args)
{pthread_detach(pthread_self());  // 分離線程,線程結束后自動釋放資源uint64_t number = (uint64_t)args;  // 將傳入的參數(線程編號)轉換為 uint64_t 類型while(true){if(ticket > 0) // 如果還有票{usleep(1000); // 模擬一些延遲,減少系統負載cout <<"thread: " << number << " ticket: " << ticket << endl; // 打印線程編號和剩余票數ticket--;  // 減少票數}else {break; // 如果沒有票了,退出循環}usleep(20);  // 再次暫停 20 微秒,模擬其他操作}return nullptr; // 線程結束時返回空指針
}int main()
{// 創建 NUM 個線程for(int i = 0; i < NUM; i++){pthread_t tid;pthread_create(&tid,nullptr,mythread,(void*)i);  // 創建線程,傳入線程編號}sleep(5); // 主線程等待 5 秒,確保子線程有足夠的時間執行cout <<"process quit ..." <<endl;  // 打印主線程退出消息return 0;
}

簡單描述:

  1. 線程數量和票數
    • 定義了一個全局變量 ticket,初始值為 1000,表示共有 1000 張票。
    • 程序創建了 10 個線程(NUM = 10),每個線程將嘗試減少 ticket 的值,模擬每個線程購買一張票。
  2. 線程函數
    • 每個線程執行 mythread 函數,函數內部通過一個 while 循環不斷檢查 ticket 是否大于 0。如果 ticket 大于 0,則線程會輸出剩余票數并減去一張票,模擬賣票操作。
    • 使用 usleep(1000) 模擬了一個小延遲,避免線程占用過多 CPU 資源,并且增加了另一個小的 usleep(20) 讓線程執行有一定的間隔。
  3. 主線程
    • 主線程創建了 10 個線程,并且等待 5 秒后退出,給子線程一些時間執行任務。

在這里插入圖片描述

潛在問題:

  1. 競態條件(Race Condition)
    • 問題描述:多個線程同時訪問并修改共享資源 ticket,可能會發生競態條件。由于 ticket-- 操作并不是原子的(即分為讀取、修改和寫入三步),多個線程在同一時間訪問 ticket 時,可能會同時讀取到相同的值并同時更新,導致票數沒有正確減少,可能會出現賣出同一張票的情況。
    • 解決方案:可以通過互斥鎖(pthread_mutex_t)來保證每次只有一個線程能修改 ticket,避免并發寫入導致的錯誤。

臨界區

臨界區(Critical Section) 是指在多線程或多進程程序中,共享資源被多個線程或進程同時訪問和修改的代碼區域。為了確保共享資源在多線程或多進程環境中的一致性和正確性,我們需要對訪問臨界區的操作進行同步控制,以避免發生競爭條件(Race Condition)。

臨界區的特點:

  1. 共享資源訪問:臨界區中的代碼通常會訪問共享資源,例如共享內存、文件、全局變量、硬件資源等。
  2. 并發執行:多個線程或進程可能同時嘗試進入臨界區,并對共享資源進行修改。
  3. 資源競爭:如果多個線程/進程在同一時刻進入臨界區并修改共享資源,就可能導致數據沖突、不一致或錯誤。

臨界區的問題:

  • 數據一致性問題:多個線程或進程同時修改共享數據,可能導致數據不一致、錯誤或丟失。
  • 資源沖突:當多個線程或進程試圖同時訪問共享資源時,可能會引發系統資源競爭,影響程序的正確性和效率。

解決方案

  • 互斥鎖(Mutex): 互斥鎖用于確保在某一時刻只有一個線程能夠訪問臨界區。當一個線程需要進入臨界區時,它會獲取互斥鎖,其他線程必須等待該線程釋放鎖后才能進入臨界區。
  • 信號量(Semaphore): 信號量可以控制對共享資源的并發訪問。通過限制允許訪問臨界區的線程數量,可以避免過多的線程同時進入臨界區。

這樣盡管可以避免競爭條件,但是這樣不能保證共享數據進行正確高效的協作,還要滿足以下4個條件:

  1. 任何兩個進程不能同時處于臨界區。
  2. 不應該對CPU的數量和速度進行任何假設。
  3. 臨界區外的進程不得阻塞其他進程。
  4. 不得使進程無期限等待進入臨界區。

臨界區的優化:

  1. 減少臨界區的長度:盡量將臨界區的代碼量減少到最小,避免過長時間占用臨界區。
  2. 避免不必要的鎖:對于只讀的共享資源,盡量避免加鎖,減少鎖帶來的性能開銷。
  3. 使用無鎖編程(Lock-Free Programming):通過原子操作(如 atomic 類型)和 CAS(Compare-And-Swap)等無鎖技術,避免傳統鎖機制帶來的性能瓶頸。

互斥鎖

互斥鎖(Mutex) 是一種用于多線程編程的同步機制,旨在防止多個線程同時訪問和修改共享資源,從而確保數據的一致性和程序的正確性。

互斥鎖初始化

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • 全局域初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 使用默認屬性初始化
  • 局部區初始化
pthread_mutex_t mutex;  // 定義一個互斥鎖變量pthread_mutex_init(&mutex, NULL);  // 初始化互斥鎖。NULL表示使用默認的屬性pthread_mutex_destroy(&mutex);  // 銷毀互斥鎖,在不再使用鎖時調用

加鎖、解鎖

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

  • pthread_mutex_lock:用于鎖定一個互斥鎖。若互斥鎖已被其他線程鎖定,則調用線程會阻塞,直到互斥鎖被釋放。

  • pthread_mutex_trylock:嘗試鎖定互斥鎖。與 pthread_mutex_lock 不同的是,它不會阻塞線程。如果鎖定成功,返回 0;如果鎖定失敗(即鎖已經被其他線程持有),則返回一個非零值。

  • pthread_mutex_unlock:用于解鎖一個已鎖定的互斥鎖。如果當前線程沒有持有該鎖,調用此函數將導致未定義的行為。

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);  // 初始化互斥鎖pthread_mutex_lock(&mutex);  // 鎖定互斥鎖
// 訪問共享資源
pthread_mutex_unlock(&mutex);  // 解鎖互斥鎖

優化搶票問題

#include<iostream>   
#include<unistd.h>   
#include<pthread.h>  
using namespace std;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;  // 定義并初始化一個互斥鎖
#define NUM  10  // 定義創建的線程數量
int ticket = 1000;  // 定義一個全局變量 ticket,初始為 1000,表示票的數量// 線程函數,用于模擬每個線程購買票
void* mythread(void* args)
{pthread_detach(pthread_self());  // 將當前線程設置為分離線程,結束后自動回收資源uint64_t number = (uint64_t)args;  // 將傳入的參數(線程編號)轉換為 uint64_t 類型while(true)  // 循環,直到票數為 0{{pthread_mutex_lock(&lock);  // 鎖定互斥鎖,確保對 ticket 資源的互斥訪問if(ticket > 0)  // 如果還有票{usleep(1000);  // 模擬工作延遲,單位為微秒(1 毫秒)cout <<"thread: " << number << " ticket: " << ticket << endl;  // 輸出當前線程編號和剩余票數ticket--;  // 票數減少}else  // 如果票數為 0,退出循環{break;}pthread_mutex_unlock(&lock);  // 解鎖,允許其他線程訪問 ticket 資源}}return nullptr;  // 返回空指針,結束線程
}int main()
{// 創建多個線程for(int i = 0; i < NUM; i++)  // 創建 NUM 個線程{pthread_t tid;  // 定義線程 IDpthread_create(&tid, nullptr, mythread, (void*)i);  // 創建線程并傳遞參數(線程編號)}sleep(5);  // 主線程休眠 5 秒,確保所有線程執行一段時間cout <<"process quit ..." <<endl;  // 輸出退出信息,表示主進程結束return 0;  // 返回 0,程序結束
}

代碼詳細注釋解析:

  1. 全局變量

    • pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;:定義了一個全局互斥鎖,初始化時就已經可用。這個鎖是為了防止多個線程同時訪問和修改 ticket 變量導致的并發問題。
    • #define NUM 10:定義了一個宏 NUM,表示需要創建的線程數量(此處為 10)。
    • int ticket = 1000;:全局變量 ticket 表示剩余票數,初始值為 1000。
  2. 線程函數 mythread

    • pthread_detach(pthread_self());:將當前線程設置為分離線程,這樣線程結束時系統會自動回收資源,無需顯式調用 pthread_join 來等待線程結束。

    • uint64_t number = (uint64_t)args;:將傳遞給線程函數的參數(線程編號)轉換為 uint64_t 類型,以便進行打印。

    • while(true)循環中,線程將不斷檢查 ticket

      是否大于 0:

      • 使用 pthread_mutex_lock(&lock); 上鎖,防止多個線程同時修改 ticket 變量,保證每次只有一個線程能訪問和修改票數。
      • 如果 ticket > 0,則輸出當前線程的編號和剩余票數,并將票數減 1。每次操作后調用 usleep(1000); 來模擬工作延時。
      • 如果 ticket 為 0,跳出循環。
      • 最后,通過 pthread_mutex_unlock(&lock); 解鎖,允許其他線程訪問共享資源。
  3. 主函數 main

    • for 循環中,創建了 10 個線程,每個線程都會執行 mythread 函數。線程編號(i)被傳遞到每個線程中,作為其唯一標識。
    • sleep(5);:主線程休眠 5 秒,以確保創建的 10 個子線程有足夠的時間執行完畢。
    • cout <<"process quit ..." <<endl;:輸出程序退出信息,表示主程序結束。

打印

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

互斥鎖封裝(RAII)

class mutex
{
public:
private:pthread_mutex_t * _mutex;  // 互斥鎖指針public:mutex(pthread_mutex_t* mutex):_mutex(mutex)  // 構造函數,初始化互斥鎖指針{//pthread_mutex_init(_mutex,nullptr);  // 互斥鎖的初始化被注釋掉了}void lock(){pthread_mutex_lock(_mutex);  // 鎖定互斥鎖}void unlock(){pthread_mutex_unlock(_mutex);  // 解鎖互斥鎖}~mutex() {}  // 析構函數,什么都不做
};class Guard
{
private:mutex _lock;  // 使用上面定義的 mutex 類來管理鎖public:Guard(pthread_mutex_t* lock):_lock(lock)  // 構造函數中鎖定互斥鎖{_lock.lock();  // 自動鎖定}~Guard()  // 析構函數中解鎖{_lock.unlock();  // 自動解鎖}
};

這段代碼的設計實現了一個典型的 RAII(資源獲取即初始化) 模式,尤其是在 Guard 類中得到了完美的體現。RAII 是 C++ 中管理資源(如內存、文件句柄、互斥鎖等)的一種設計模式。在該模式下,資源在對象的構造函數中獲取,在對象的析構函數中釋放,這樣可以確保即使發生異常,也能正確釋放資源,避免資源泄漏和死鎖。

// 構造函數中鎖定互斥鎖
{
_lock.lock(); // 自動鎖定
}

~Guard()  // 析構函數中解鎖
{_lock.unlock();  // 自動解鎖
}

};


這段代碼的設計實現了一個典型的 **RAII(資源獲取即初始化)** 模式,尤其是在 `Guard` 類中得到了完美的體現。RAII 是 C++ 中管理資源(如內存、文件句柄、互斥鎖等)的一種設計模式。在該模式下,資源在對象的構造函數中獲取,在對象的析構函數中釋放,這樣可以確保即使發生異常,也能正確釋放資源,避免資源泄漏和死鎖。

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

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

相關文章

《Stable Diffusion 3.0企業級落地指南》——技術賦能與商業價值的深度融合實踐

Stable Diffusion 3.0&#xff08;SD3&#xff09;作為當前多模態生成式AI技術的集大成者&#xff0c;憑借其創新的擴散Transformer架構&#xff08;DiT&#xff09;、流匹配&#xff08;Flow Matching&#xff09;技術以及超分辨率生成能力&#xff0c;正在重塑企業內容生產的…

基于本地模型+多級校驗設計的高效緩存,有效節省token數量(有點雞肋doge)。

前言 我是基于token有限而考慮的一個省錢方案&#xff0c;還能夠快速返回結果&#xff0c;但是劣勢也很明顯&#xff0c;設計不好容易出問題&#xff0c;就如下面所介紹的語義飄逸和緩存污染&#xff0c;我認為在自己學習大模型的過程用來省錢非常可以&#xff0c;再加上學習過…

網絡安全全知識圖譜:威脅、防護、管理與發展趨勢詳解

1 網絡安全基礎概念 1.1 什么是網絡安全 網絡安全是指通過技術、管理和法律等手段&#xff0c;保護計算機網絡系統中的硬件、軟件及其系統中的數據&#xff0c;不因偶然的或者惡意的原因而遭受到破壞、更改、泄露&#xff0c;確保系統連續可靠正常地運行&#xff0c;網絡服務不…

遠控安全進階之戰:TeamViewer/ToDesk/向日葵設備安全策略對比

【作者主頁】Francek Chen 【文章摘要】在數字化時代&#xff0c;卓越的遠程控制軟件需兼顧功能與體驗&#xff0c;包括流暢連接、高清畫質、低門檻UI設計、毫秒級延遲及多功能性&#xff0c;同時要有獨樹一幟的遠控安全技術&#xff0c;通過前瞻性安全策略阻擋網絡風險&#x…

Steam發布游戲過程的若干問題

我沒有想到在Steam發布游戲的過程會比做游戲的過程更困難&#xff0c;更惡心。 注冊Steamworks 稅務采訪 稅務采訪部分填的地址要和后面它們要求你發證件照片里的地址一樣。護照里因為沒有地址不會通過&#xff0c;我用的駕照里面有地址。沒有駕照可以用身份證。 應用準備界…

開搞:第四個微信小程序:圖上縣志

原因&#xff1a;我換了一個微信號來搞&#xff0c;因為用同一個用戶&#xff0c;備案只能一個個的來。這樣不行。所以我換了一個。原來注冊過小程序。現在修改即可。注意做好計劃后&#xff0c;速度備案和審核&#xff0c;不然你時間浪費不起。30元花起。 結構&#xff1a; -…

第三十七天打卡

知識點回顧&#xff1a; 過擬合的判斷&#xff1a;測試集和訓練集同步打印指標模型的保存和加載 僅保存權重保存權重和模型保存全部信息checkpoint&#xff0c;還包含訓練狀態 早停策略 作業&#xff1a;對信貸數據集訓練后保存權重&#xff0c;加載權重后繼續訓練50輪&#x…

Java高頻面試之并發編程-21

hello啊&#xff0c;各位觀眾姥爺們&#xff01;&#xff01;&#xff01;本baby今天又來報道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面試官&#xff1a;詳細說說AQS AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是 Java 并發包&#xff08;java.util.concurre…

按鍵狀態機

原工程地址&#xff1a;https://github.com/candylife9/state_machine_example 視頻&#xff1a;C語言之狀態機編程_02_狀態機使用案例分析_嗶哩嗶哩_bilibili 我覺得講的挺好的。 來自豆包封裝的通用接口 頭文件 /*** file key_state_machine.h* brief 通用按鍵狀態機接口…

華為OD機試真題——新學校選址(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 A卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C++、GO六種語言的最佳實現方式; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析; 本文收錄于專欄:《2025華為OD真題目錄+全流程解析+備考攻略+經驗分…

歐拉操作系統下安裝hadoop集群

背景&#xff1a;歐拉操作系統下安裝CDH集群的時候&#xff0c;需要安裝python2.7.5&#xff0c;但是本身歐拉系統對python2的支持可能沒有那么好&#xff0c;所以考慮搭建原生的hadoop集群。 基礎環境如下 組件名稱組件版本歐拉VERSION“22.03 (LTS-SP4)”jdkopenjdk versio…

SQL語句的執行流程

文章目錄 一、執行流程二、建立連接三、預處理器四、解析器4.1 詞法分析4.2 語法分析4.3 語義分析 五、優化器六、執行器七、返回結果 一、執行流程 階段主要功能關鍵組件1. 建立連接身份驗證、權限檢查連接器2. 預處理器緩存檢查、SQL預處理查詢緩存3. 解析器詞法分析、語法分…

TiDB:從快速上手到核心原理與最佳實踐

文章目錄 引言第一部分&#xff1a;TiDB快速體驗與實踐指南1. TiDB概述2. TiDB部署方式2.1 本地測試環境部署2.2 生產環境部署2.3 Kubernetes部署2.4 云服務 3. TiDB基本操作3.1 連接TiDB3.2 數據庫和表操作3.3 分區表3.4 事務操作 4. 數據遷移到TiDB4.1 從MySQL遷移4.2 使用Ti…

總結:進程和線程的聯系和區別

前言:通過學習javaEE初階中的多線程章節后加上我自己的理解,想來總結一下線程和進程的聯系和區別. 一來是能更好地復習知識,二來是為了記錄我的學習路程,相信未來的我回首不會忘記這段難忘的經歷. 1.進程 先來談談進程:進程是操作系統中資源分配的基本單位. 1)進程的執行方…

邊緣云的定義、實現與典型應用場景!與傳統云計算的區別!

一、什么是邊緣云&#xff1f;? 邊緣云是一種?分布式云計算架構?&#xff0c;將計算、存儲和網絡資源部署在?靠近數據源或終端用戶的網絡邊緣側?&#xff08;如基站、本地數據中心或終端設備附近&#xff09;&#xff0c;而非傳統的集中式云端數據中心。 ?核心特征?&…

海康威視攝像頭C#開發指南:從SDK對接到安全增強與高并發優化

一、海康威視SDK核心對接流程?? 1. ??開發環境準備?? ??官方SDK獲取??&#xff1a;從海康開放平臺下載最新版SDK&#xff08;如HCNetSDK.dll、PlayCtrl.dll&#xff09;。??依賴項安裝??&#xff1a;確保C運行庫&#xff08;如vcredist_x86.exe&#xff09;與S…

《軟件工程》第 9 章 - 軟件詳細設計

目錄 9.1 詳細設計的任務與過程模型 9.2 用例設計 9.2.1 設計用例實現方案 9.2.2 構造設計類圖 9.2.3 整合并優化用例實現方案 9.3 子系統設計 9.3.1 確立內部設計元素 9.3.2 導出設計類圖 9.4 構件設計 9.5 類設計 9.5.1 精化類間關系 9.5.2 精化屬性和操作 9.5.…

spring+tomcat 用戶每次發請求,tomcat 站在線程的角度是如何處理用戶請求的,spinrg的bean 是共享的嗎

對于 springtomcat 用戶每次發請求&#xff0c;tomcat 站在線程的角度是如何處理的 比如 bio nio apr 等情況 tomcat 配置文件中 maxThreads 的數量是相對于誰來說的&#xff1f; 以及 spring Controller 中的全局變量:各種bean 對于線程來說是共享的嗎&#xff1f; 一、Tomca…

存儲引擎系列--LSM不同Compaction策略性能分析對比

本文介紹一下參考論文里的Compaction性能分析部分,作者在RocksDB的基礎上做了多種策略的改造,然后提出了benchmarking方法論,關注compaction性能的哪些維度,并對結果進行分析。 一、Standardization of Compaction Strategies 1.1 實驗平臺的選擇 作者選擇了RocksDB作為…

leetcode 3559. Number of Ways to Assign Edge Weights II

leetcode 3559. Number of Ways to Assign Edge Weights II 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;3559. Number of Ways to Assign Edge Weights II 1. 解題思路 這一題是題目3558. Number of Ways to Assign Edge Weights I的進階版本。 對于題目3558來說&#xf…