【Linux】同步原理剖析及模擬BlockQueue生產消費模型

📢博客主頁:https://blog.csdn.net/2301_779549673
📢博客倉庫:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
📢本文由 JohnKi 原創,首發于 CSDN🙉
📢未來很長,值得我們全力奔赴更美好的生活?

在這里插入圖片描述

在這里插入圖片描述

文章目錄

  • 📢前言
  • 🏳??🌈一、線程同步概念
  • 🏳??🌈二、為什么需要線程同步?
    • 2.1 防止數據競爭(Data Race)?
    • 2.2 保證操作的原子性
    • 2.?3 協調線程間的執行順序
    • 2.?4 避免資源爭用(如文件、網絡連接)?
  • 🏳??🌈線程同步的常見手段
  • 🏳??🌈三、什么是生產消費者模型
    • 3.1 三種關系
    • 3.2 兩個角色
    • 3.3 一個場景
  • 🏳??🌈四、以阻塞隊列模擬多生產消費者模型
    • 4.1 成員名
    • 4.2 構造函數和析構函數
    • 4.3 模擬的生產者和消費者
    • 4.4 模擬生產、消費者過程
  • 🏳??🌈五、整體代碼
    • 5.1 BlockQueue.hpp
    • 5.2 Main.cc
    • 5.3 Makefile
  • 👥總結


📢前言

緊接上一回的 從互斥原理到C++ RAII封裝實踐 筆者這回介紹一下線程中幾乎與互斥一樣重要的同步原理,

還有一點,筆者之后的封裝都會使用之前博客中封裝好的容器,需要的可以去倉庫或者前面的博客中自取。

所需所用的都放在了這個倉庫中
在這里插入圖片描述


🏳??🌈一、線程同步概念

條件變量:當一個線程互斥地訪問某個變量時,它可能發現在其它線程改變狀態之前,它什么也做不了。例如一個線程訪問隊列時,發現隊列為空,它只能等待,只到其它線程將一個節點添加到隊列中。這種情況就需要用到條件變量。

線程同步 指在多線程編程中,通過特定機制協調多個線程的執行順序,確保它們對共享資源?(如內存、文件、硬件等)的訪問安全有序。核心目標是防止并發訪問導致的數據混亂、邏輯錯誤或資源沖突

🏳??🌈二、為什么需要線程同步?

2.1 防止數據競爭(Data Race)?

?問題:多個線程同時讀寫共享數據時,執行順序不確定,可能導致數據不一致。

int balance = 100;  // 共享變量// 線程A執行:存入200
balance += 200;  // 線程B執行:取出150
balance -= 150;
  • 未同步時:若線程A和B同時讀取balance=100,最終結果可能是100+200-150=150(正確應為150)或100-150+200=150,但若操作交叉執行(如A讀后B寫),可能得到錯誤值(如-50)。

2.2 保證操作的原子性

?問題:單個操作(如i++)在底層可能對應多條機器指令,線程切換會導致操作未完成就被中斷

; x86的i++實際步驟:
mov eax, [i]  ; 讀取i到寄存器
inc eax       ; 寄存器加1
mov [i], eax  ; 寫回內存
  • 若線程A執行到inc eax后被切換,線程B修改了i,線程A恢復后會將舊值寫回,導致結果錯誤。

2.?3 協調線程間的執行順序

?場景:某些任務需要線程按特定順序執行。
?生產者-消費者模型:消費者線程需等待生產者生成數據后再讀取。
?任務依賴:線程B必須在線程A完成初始化后才能執行。

2.?4 避免資源爭用(如文件、網絡連接)?

?問題:多個線程同時寫入同一文件或占用同一網絡端口,會導致數據錯亂或程序崩潰。

🏳??🌈線程同步的常見手段

在這里插入圖片描述
同步問題的嚴重后果
?數據不一致:程序輸出錯誤,如銀行賬戶余額異常。
?程序崩潰:多線程同時釋放內存導致雙重釋放(Double Free)。
?死鎖(Deadlock)?:線程互相等待對方釋放鎖,導致永久阻塞。

說白了,線程同步就是一種為了統一管理生產消費者模型的一種機制

🏳??🌈三、什么是生產消費者模型

在這里插入圖片描述

3.1 三種關系

在這里插入圖片描述

3.2 兩個角色

生產者,模擬是同數據的那方
消費者,取走數據的那方

3.3 一個場景

所有生產消費所用的數據都是在中間的 “超市” 中進行
在這里插入圖片描述

🏳??🌈四、以阻塞隊列模擬多生產消費者模型

下圖是以阻塞隊列模擬多生產消費者模型的基本過程

也就是有兩類線程(生產者和消費者),從同一個場景(blockqueue)中放入和拿出數據的過程
在這里插入圖片描述

4.1 成員名

我們利用現有的庫函數對環境進行一下封裝,再利用一個隊列模擬臨界資源

    private:std::queue<T> _q;               // 保存數據的容器,臨界資源int _cap;                       // bq最大容量pthread_mutex_t _mutex;         // 互斥pthread_cond_t _productor_cond; // 生產者條件變量pthread_cond_t _consumer_cond;  // 消費者條件變量int _cwait_num;                 // 當前等待的消費者數量int _pwait_num;                 // 當前等待的生產者數量};

4.2 構造函數和析構函數

		BlockQueue(int cap = gcap) : _cap(cap), _cwait_num(0), _pwait_num(0){pthread_mutex_init(&_mutex, nullptr);         // 創建互斥鎖pthread_cond_init(&_productor_cond, nullptr); // 生產者條件變量pthread_cond_init(&_consumer_cond, nullptr);  // 消費者條件變量}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_productor_cond);pthread_cond_destroy(&_consumer_cond);}

4.3 模擬的生產者和消費者

void Equeue(const T &in) // 生產者{pthread_mutex_lock(&_mutex);// 你想放數據,就能放數據嗎??生產數據是有條件的!// 結論1: 在臨界區中等待是必然的(目前)while (IsFull()) // 5. 對條件進行判斷,為了防止偽喚醒,我們通常使用while進行判斷!{std::cout << "生產者進入等待..." << std::endl;// 2. 等是,釋放_mutex_pwait_num++;pthread_cond_wait(&_productor_cond, &_mutex); // wait的時候,必定是持有鎖的!!是有問題的!_pwait_num--;// 3. 返回,線程被喚醒&&重新申請并持有鎖(它會在臨界區內醒來!)std::cout << "生產者被喚醒..." << std::endl;}// 4. if(IsFull())不滿足 || 線程被喚醒_q.push(in); // 生產// 肯定有數據!if(_cwait_num){std::cout << "叫醒消費者" << std::endl;pthread_cond_signal(&_consumer_cond);}pthread_mutex_unlock(&_mutex);}void Pop(T *out) // 消費者{pthread_mutex_lock(&_mutex);while(IsEmpty()){std::cout << "消費者進入等待..." << std::endl;_cwait_num++;pthread_cond_wait(&_consumer_cond, &_mutex); // 偽喚醒_cwait_num--;std::cout << "消費者被喚醒..." << std::endl;}// 4. if(IsEmpty())不滿足 || 線程被喚醒*out = _q.front();_q.pop();// 肯定有空間if(_pwait_num){std::cout << "叫醒生產者" << std::endl;pthread_cond_signal(&_productor_cond);}pthread_mutex_unlock(&_mutex);}

4.4 模擬生產、消費者過程

我們假設生產速度小于消費速度,相當于我們沒生產一個對象后需要花費一定的時間,但是消費者一直就緒,就要等生產者生產出來

void *Consumer(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while(true){int data;// 1. 從bq拿到數據bq->Pop(&data);// 2.做處理printf("Consumer, 消費了一個數據: %d\n", data);}
}void *Productor(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);int data = 10;while (true){sleep(2);// 1. 從外部獲取數據// data = 10; // 有數據???// 2. 生產到bq中bq->Equeue(data);printf("producter 生產了一個數據: %d\n", data);data++;}
}

🏳??🌈五、整體代碼

5.1 BlockQueue.hpp

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>namespace BlockQueueModule
{static const int gcap = 10;template <typename T>class BlockQueue{private:bool IsFull() { return _q.size() == _cap; }bool IsEmpty() { return _q.empty(); }public:BlockQueue(int cap = gcap) : _cap(cap), _cwait_num(0), _pwait_num(0){pthread_mutex_init(&_mutex, nullptr);         // 創建互斥鎖pthread_cond_init(&_productor_cond, nullptr); // 生產者條件變量pthread_cond_init(&_consumer_cond, nullptr);  // 消費者條件變量}void Equeue(const T &in) // 生產者{pthread_mutex_lock(&_mutex);// 你想放數據,就能放數據嗎??生產數據是有條件的!// 結論1: 在臨界區中等待是必然的(目前)while (IsFull()) // 5. 對條件進行判斷,為了防止偽喚醒,我們通常使用while進行判斷!{std::cout << "生產者進入等待..." << std::endl;// 2. 等是,釋放_mutex_pwait_num++;pthread_cond_wait(&_productor_cond, &_mutex); // wait的時候,必定是持有鎖的!!是有問題的!_pwait_num--;// 3. 返回,線程被喚醒&&重新申請并持有鎖(它會在臨界區內醒來!)std::cout << "生產者被喚醒..." << std::endl;}// 4. if(IsFull())不滿足 || 線程被喚醒_q.push(in); // 生產// 肯定有數據!if(_cwait_num){std::cout << "叫醒消費者" << std::endl;pthread_cond_signal(&_consumer_cond);}pthread_mutex_unlock(&_mutex);}void Pop(T *out) // 消費者{pthread_mutex_lock(&_mutex);while(IsEmpty()){std::cout << "消費者進入等待..." << std::endl;_cwait_num++;pthread_cond_wait(&_consumer_cond, &_mutex); // 偽喚醒_cwait_num--;std::cout << "消費者被喚醒..." << std::endl;}// 4. if(IsEmpty())不滿足 || 線程被喚醒*out = _q.front();_q.pop();// 肯定有空間if(_pwait_num){std::cout << "叫醒生產者" << std::endl;pthread_cond_signal(&_productor_cond);}pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_productor_cond);pthread_cond_destroy(&_consumer_cond);}private:std::queue<T> _q;               // 保存數據的容器,臨界資源int _cap;                       // bq最大容量pthread_mutex_t _mutex;         // 互斥pthread_cond_t _productor_cond; // 生產者條件變量pthread_cond_t _consumer_cond;  // 消費者條件變量int _cwait_num;                 // 當前等待的消費者數量int _pwait_num;                 // 當前等待的生產者數量};
}

5.2 Main.cc

#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>using namespace BlockQueueModule;void *Consumer(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while(true){int data;// 1. 從bq拿到數據bq->Pop(&data);// 2.做處理printf("Consumer, 消費了一個數據: %d\n", data);}
}void *Productor(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);int data = 10;while (true){sleep(2);// 1. 從外部獲取數據// data = 10; // 有數據???// 2. 生產到bq中bq->Equeue(data);printf("producter 生產了一個數據: %d\n", data);data++;}
}int main()
{// 交易場所,不僅僅可以用來進行傳遞數據// 傳遞任務!!!v1: 對象 v2BlockQueue<int> *bq = new BlockQueue<int>(5); // 共享資源 -> 臨界資源// 單生產,單消費pthread_t c1, p1, c2, p2, p3;pthread_create(&c1, nullptr, Consumer, bq);pthread_create(&c2, nullptr, Consumer, bq);pthread_create(&p1, nullptr, Productor, bq);pthread_create(&p2, nullptr, Productor, bq);pthread_create(&p3, nullptr, Productor, bq);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(p3, nullptr);delete bq;return 0;
}

5.3 Makefile

bin=bq
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc$(cc) -c $< -std=c++17.PHONY:clean
clean:rm -f $(bin) $(obj).PHONY:test
test:echo $(src)echo $(obj)

👥總結

本篇博文對 同步原理剖析及模擬多消費者模型 做了一個較為詳細的介紹,不知道對你有沒有幫助呢

覺得博主寫得還不錯的三連支持下吧!會繼續努力的~

請添加圖片描述

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

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

相關文章

光流 | 基于KLT算法的人臉檢測與跟蹤原理及公式,算法改進,matlab代碼

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 人臉檢測與跟蹤 一、KLT算法原理與分析1. 核心思想2. 數學模型二、人臉…

<數據集>軌道異物識別數據集<目標檢測>

數據集下載鏈接&#xff1a;https://download.csdn.net/download/qq_53332949/90527370 數據集格式&#xff1a;VOCYOLO格式 圖片數量&#xff1a;1659張 標注數量(xml文件個數)&#xff1a;1659 標注數量(txt文件個數)&#xff1a;1659 標注類別數&#xff1a;6 標注類別…

LabVIEW液壓振動錘控制系統

在現代工程機械領域&#xff0c;液壓振動錘的高效與精準控制日益顯得重要。本文通過LabVIEW軟件&#xff0c;展開液壓振動錘啟停共振控制技術的研究與應用&#xff0c;探討如何通過改進控制系統來優化液壓振動錘的工作性能&#xff0c;確保其在復雜工況下的穩定性與效率。 ? …

【開源寶藏】30天學會CSS - DAY7 第七課 CSS 關鍵幀打造Preloader 追逐動畫

你的代碼實現了一個 方形軌跡預加載動畫&#xff08;Preloader Animation&#xff09;&#xff0c;其中三個 span 元素沿著一個 22 網格 軌跡循環移動。現在&#xff0c;我們將 拆解核心實現步驟&#xff0c;讓你能一步步理解并調整動畫效果。 第 0 步&#xff1a;項目概覽 你…

在shell腳本內部獲取該腳本所在目錄的絕對路徑

目錄 需求描述 方法一&#xff1a;使用 dirname 和 readlink 命令 方法二&#xff1a;使用 BASH_SOURCE 變量 方法三&#xff1a;僅使用純 Bash 實現 需求描述 工作中經常有這樣情況&#xff0c;需要在腳本內部獲取該腳本自己所在目錄的絕對路徑。 假如有一個腳本/a/b/c/…

常考計算機操作系統面試習題(一下)

目錄 操作系統基本類型 操作系統的功能 操作系統的主要任務 進程與線程 進程狀態轉變 內存管理 文件系統與文件管理 虛擬存儲器 設備管理 磁盤調度 死鎖 信號量機制 文件打開與管理 進程與線程的互斥與同步 進程同步 進程調度 文件分配磁盤塊的方法 程序執行…

GPT-SoVITS本地部署:低成本實現語音克隆遠程生成音頻全流程實戰

文章目錄 前言1.GPT-SoVITS V2下載2.本地運行GPT-SoVITS V23.簡單使用演示4.安裝內網穿透工具4.1 創建遠程連接公網地址 5. 固定遠程訪問公網地址 前言 今天要給大家安利一個絕對能讓你大呼過癮的聲音黑科技——GPT-SoVITS&#xff01;這款由花兒不哭大佬精心打造的語音克隆神…

JVM(基礎篇)

一.初識JVM 1.什么是JVM JVM全稱Java Virtyal Machine&#xff0c;中文譯名 Java虛擬機 。JVM本質上是一個運行在計算機上的程序&#xff0c;他的職責是運行Java字節碼文件(將字節碼解釋成機器碼)。 2.JVM的功能 解釋和運行&#xff1a;對字節碼文件中的指令號&#xff0c;實時…

【高并發內存池】第四彈---深入理解PageCache:整體設計、核心實現及Span獲取策略詳解

?個人主頁&#xff1a; 熬夜學編程的小林 &#x1f497;系列專欄&#xff1a; 【C語言詳解】 【數據結構詳解】【C詳解】【Linux系統編程】【Linux網絡編程】【項目詳解】 目錄 1、pagecache 1.1、整體設計 1.2、核心實現 1.3、獲取Span 1.3.1、獲取一個非空的Span 1.3…

深入理解C語言數據結構之快速排序三路劃分

在數據結構和算法的世界里&#xff0c;排序算法是基石一般的存在。快速排序作為一種高效的排序算法&#xff0c;以其平均情況下的優秀時間復雜度而被廣泛應用。今天&#xff0c;讓我們深入探討快速排序的一種變體——三路劃分的快速排序&#xff0c;看看它是如何在C語言中施展魔…

Java實現后量子密碼(PQC)與國密算法(SM4)混合加密

以下是使用Java實現一種后量子密碼(PQC)與國密算法(SM4)混合加密的示例方案。該方案結合了后量子密碼的抗量子特性與國密算法的國產化合規要求,適合需要雙重安全保障的場景。 一 . 方案驗證 1.代碼截圖 2.運行測試 二 . 方案設計 密鑰交換:使用后量子密碼(如Kyber)生…

【SQL Server數據庫備份詳細教程】

&#x1f3a5;博主&#xff1a;程序員不想YY啊 &#x1f4ab;CSDN優質創作者&#xff0c;CSDN實力新星&#xff0c;CSDN博客專家 &#x1f917;點贊&#x1f388;收藏?再看&#x1f4ab;養成習慣 ?希望本文對您有所裨益&#xff0c;如有不足之處&#xff0c;歡迎在評論區提出…

SpringBoot古典舞在線交流平臺設計與實現

隨著古典舞文化的普及&#xff0c;越來越多的人希望通過線上平臺交流學習。幽絡源作為一站式綜合平臺&#xff0c;致力于為用戶提供免費源碼、技術教程及網絡兼職資源。本文將詳細介紹基于SpringBoot的古典舞在線交流平臺的設計與實現&#xff0c;幫助開發者快速搭建一個功能完…

關于絕對時間、人類時間、本地時間、時區時間的對比分析,結合編程場景(如Java)進行說明

以下是關于絕對時間、人類時間、本地時間、時區時間的對比分析&#xff0c;結合編程場景&#xff08;如Java&#xff09;進行說明&#xff1a; 1. 定義與核心區別 (1) 絕對時間&#xff08;Absolute Time&#xff09; 定義&#xff1a;不受時區影響&#xff0c;以固定時間起點…

go語言中的strings庫

strings庫 func EqualFold func EqualFold(s, t string) bool判斷兩個utf-8編碼字符串&#xff08;將unicode大寫、小寫、標題三種格式字符視為相同&#xff09;是否相同。 func main() {fmt.Println(strings.EqualFold("hello", "hello")) //truefmt.…

Git沖突解決

目錄 一、Git沖突產生的原因二、解決Git沖突的步驟1. 發現沖突2. 查看沖突文件3. 手動解決沖突4. 提交解決后的代碼5. 完成合并 三、預防Git沖突的小技巧四、總結 在團隊協作開發中&#xff0c;Git沖突是常見的問題。當多個開發者同時修改了同一個文件的不同部分&#xff0c;然…

Spring AOP + RocketMQ 實現企業級操作日志異步采集(實戰全流程)

Spring AOP + RocketMQ 實現企業級操作日志異步采集(實戰全流程) ?? 項目背景 在企業級微服務架構中,記錄操作日志是一項剛需。傳統方式常使用數據庫直接寫入或通過 Feign 調用日志微服務,但這樣存在耦合高、主流程阻塞、擴展性差等問題。 為此,我們將使用: Spring …

Git Flow 分支管理策略

優勢 清晰的分支結構&#xff1a;每個分支都有明確的用途&#xff0c;便于團隊協作。 穩定的 master 分支&#xff1a;生產環境代碼始終穩定。 靈活的發布管理&#xff1a;通過發布分支和熱修復分支&#xff0c;可以靈活管理版本發布和緊急修復。 主要分支 master 分支 代表…

Altium Designer數模電學習筆記

模電 電容 **退耦&#xff1a;**利用通交阻直&#xff0c;將看似直流的信號中的交流成分濾除 &#xff08;一般用在給MPU供電&#xff0c;盡量小一些&#xff0c;10nf~100nf~1uf以下&#xff09; **濾波&#xff1a;**也可以理解為給電容充電&#xff0c;讓電容在電平為低時…

光譜儀與光譜相機的核心區別與協同應用

一、核心功能與數據維度 ?光譜儀? ?功能定位?&#xff1a;專注單點或線狀區域的光譜分析&#xff0c;通過色散元件&#xff08;光柵/棱鏡&#xff09;分離波長&#xff0c;生成一維或二維光譜曲線&#xff0c;用于量化光強、吸收率等參數?。 ?數據維度?&#xff1a;輸…