設計模式 之 生產消費者模型 (C++)

文章目錄

  • 設計模式 之 生產消費者模型 (C++)
    • 引言
    • 生產消費者模型的基本概念
    • 為什么需要生產消費者模型
    • 應用場景:
    • C++ 實現生產消費者模型
      • 代碼示例
      • 代碼詳細解釋
        • 共享資源和同步機制
        • 生產者函數 `producer()`
        • 消費者函數 `consumer()`
        • 主函數 `main()`
    • 注意事項
    • 總結

設計模式 之 生產消費者模型 (C++)

引言

在多線程編程的世界里,生產消費者模型是一個經典且實用的設計模式。它能夠高效地解決多個線程之間的數據共享和協作問題,在很多實際場景中都有廣泛的應用,比如消息隊列系統、數據處理流水線等。本文將深入探討 C++ 中生產消費者模型的原理、實現方式以及相關的注意事項。

生產消費者模型的基本概念

生產消費者模型主要包含三個核心部分:生產者、消費者和緩沖區。

  • 生產者:負責生成數據或任務,并將其放入緩沖區。
  • 緩沖區:作為生產者和消費者之間的共享區域,用于臨時存儲生產者產生的數據,起到解耦和協調生產者與消費者速度差異的作用。
  • 消費者:從緩沖區中取出數據或任務進行處理。

為什么需要生產消費者模型

想象一下,如果沒有緩沖區,生產者和消費者直接進行交互,那么它們的執行速度必須嚴格匹配。一旦生產者生產速度過快,消費者可能來不及處理;反之,若消費者處理速度過快,生產者又可能跟不上節奏。這就會導致程序的效率低下,甚至出現數據丟失或線程阻塞等問題。而引入緩沖區后,生產者和消費者可以獨立運行,各自按照自己的速度進行生產和消費,大大提高了程序的并發性能和靈活性。

應用場景:

在軟件開發里,Web 服務器把客戶端請求作為生產者數據存入請求隊列,工作線程作為消費者處理請求,提升并發處理能力;圖形圖像處理軟件中,讀取圖像數據的線程是生產者,處理數據的線程是消費者,實現讀寫并行。系統設計方面,消息隊列系統里生產者服務發消息,消費者服務訂閱消費,實現異步解耦;數據庫讀寫分離時,寫操作是生產者將請求入隊,寫線程或服務器作為消費者執行。

C++ 實現生產消費者模型

代碼示例

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>// 定義一個共享隊列作為緩沖區
std::queue<int> buffer;
// 定義互斥鎖,用于保護共享資源
std::mutex mtx;
// 定義條件變量,用于線程間的同步
std::condition_variable not_full;
std::condition_variable not_empty;
// 定義緩沖區的最大容量
const int MAX_SIZE = 5;// 生產者函數
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);// 等待緩沖區有空閑位置not_full.wait(lock, [] { return buffer.size() < MAX_SIZE; });// 生產數據buffer.push(i);std::cout << "生產者生產了數據 " << i << std::endl;// 通知消費者緩沖區有新數據not_empty.notify_one();lock.unlock();// 模擬生產時間std::this_thread::sleep_for(std::chrono::seconds(1));}
}// 消費者函數
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);// 等待緩沖區有數據not_empty.wait(lock, [] { return!buffer.empty(); });// 消費數據int data = buffer.front();buffer.pop();std::cout << "消費者消費了數據 " << data << std::endl;// 通知生產者緩沖區有空閑位置not_full.notify_one();lock.unlock();// 模擬消費時間std::this_thread::sleep_for(std::chrono::seconds(2));}
}int main() {// 創建生產者和消費者線程std::thread producer_thread(producer);std::thread consumer_thread(consumer);// 等待生產者線程結束producer_thread.join();// 由于消費者線程是無限循環,這里簡單等待一段時間后強制結束程序std::this_thread::sleep_for(std::chrono::seconds(20));// 可以考慮更優雅的方式結束消費者線程return 0;
}

代碼詳細解釋

共享資源和同步機制
  • std::queue<int> buffer:這是一個標準庫中的隊列,作為生產者和消費者之間的共享緩沖區。它可以存儲整數類型的數據,你可以根據實際需求將其改為存儲其他類型的數據。
  • std::mutex mtx:互斥鎖是線程同步的重要工具,用于保護對共享資源(這里是 buffer)的訪問。在多線程環境中,多個線程可能同時嘗試訪問和修改 buffer,使用互斥鎖可以確保同一時間只有一個線程能夠對其進行操作,避免數據競爭和不一致的問題。
  • std::condition_variable not_fullstd::condition_variable not_empty:條件變量用于線程間的同步通信。not_full 用于通知生產者緩沖區有空閑位置,當緩沖區已滿時,生產者線程會等待這個條件變量;not_empty 用于通知消費者緩沖區有新數據,當緩沖區為空時,消費者線程會等待這個條件變量。
  • MAX_SIZE:定義了緩沖區的最大容量,避免緩沖區無限增長導致內存溢出。
生產者函數 producer()
  • std::unique_lock<std::mutex> lock(mtx):使用 std::unique_lock 來管理互斥鎖。它會在構造時自動鎖定互斥鎖,在析構時自動解鎖,確保鎖的正確使用,避免忘記解鎖導致死鎖。
  • not_full.wait(lock, [] { return buffer.size() < MAX_SIZE; }):這是條件變量的核心用法。wait 函數會先釋放互斥鎖,然后阻塞當前線程,直到條件變量被通知且謂詞(這里是 buffer.size() < MAX_SIZE)為真。當其他線程調用 not_full.notify_one()not_full.notify_all() 時,該線程會被喚醒,重新獲取互斥鎖并檢查謂詞。如果謂詞為真,則繼續執行后續代碼;否則,繼續等待。
  • buffer.push(i):將生產的數據放入緩沖區。
  • not_empty.notify_one():通知一個等待在 not_empty 條件變量上的消費者線程,緩沖區有新數據可供消費。
  • lock.unlock():手動解鎖互斥鎖,因為 std::this_thread::sleep_for 期間不需要持有鎖,釋放鎖可以讓其他線程有機會訪問共享資源。
  • std::this_thread::sleep_for(std::chrono::seconds(1)):模擬生產數據所需的時間。
消費者函數 consumer()
  • 與生產者函數類似,使用 std::unique_lock 鎖定互斥鎖,調用 not_empty.wait 等待緩沖區有數據。
  • int data = buffer.front(); buffer.pop();:從緩沖區取出數據進行消費。
  • not_full.notify_one():通知一個等待在 not_full 條件變量上的生產者線程,緩沖區有空閑位置。
  • 同樣使用 std::this_thread::sleep_for 模擬消費數據所需的時間。
主函數 main()
  • 創建生產者和消費者線程,并啟動它們。
  • 使用 producer_thread.join() 等待生產者線程結束。
  • 由于消費者線程是一個無限循環,這里簡單地等待 20 秒后程序結束。在實際應用中,需要更優雅的方式來結束消費者線程,例如使用標志位或其他同步機制。

注意事項

  • 線程安全:在多線程編程中,線程安全是至關重要的。確保對共享資源的訪問都使用互斥鎖進行保護,避免數據競爭和不一致的問題。
  • 條件變量的使用:條件變量的 wait 函數一定要結合謂詞使用,避免虛假喚醒。虛假喚醒是指線程在沒有收到明確通知的情況下被喚醒,使用謂詞可以確保線程只有在滿足特定條件時才會繼續執行。
  • 線程結束處理:消費者線程通常是一個無限循環,需要考慮如何優雅地結束它。可以使用一個標志位,當生產者線程結束后,設置該標志位,消費者線程在每次循環時檢查該標志位,若標志位被設置,則退出循環。

總結

生產消費者模型是一種強大的多線程編程模式,通過引入緩沖區和同步機制,實現了生產者和消費者之間的高效協作。在 C++ 中,我們可以使用標準庫提供的互斥鎖和條件變量來實現線程安全的生產消費者模型。在實際應用中,要根據具體需求合理設計緩沖區的大小和生產消費的邏輯,同時注意線程安全和線程結束的處理,以確保程序的穩定性和性能。希望本文能幫助你更好地理解和應用生產消費者模型。

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

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

相關文章

Spring Boot 項目開發流程全解析

目錄 引言 一、開發環境準備 二、創建項目 三、項目結構 四、開發業務邏輯 1.創建實體類&#xff1a; 2.創建數據訪問層&#xff08;DAO&#xff09;&#xff1a; 3.創建服務層&#xff08;Service&#xff09;&#xff1a; 4.創建控制器層&#xff08;Controller&…

數據結構課程設計(java實現)---九宮格游戲,也稱幻方

【問題描述】 九宮格&#xff0c;一款數字游戲&#xff0c;起源于河圖洛書&#xff0c;與洛書是中國古代流傳下來的兩幅神秘圖案&#xff0c;歷來被認為是河洛文化的濫觴&#xff0c;中華文明的源頭&#xff0c;被譽為"宇宙魔方"。九宮格游戲對人們的思維鍛煉有著極大…

GPT-4.5 怎么樣?如何升級使用ChatGPTPlus/Pro? GPT-4.5設計目標是成為一款非推理型模型的巔峰之作

GPT-4.5 怎么樣&#xff1f;如何升級使用ChatGPTPlus/Pro? GPT-4.5設計目標是成為一款非推理型模型的巔峰之作 今天我們來說說上午發布的GPT-4.5&#xff0c;接下來我們說說GPT4.5到底如何&#xff0c;有哪些功能&#xff1f;有哪些性能提升&#xff1f;怎么快速使用到GPT-4.…

【vscode-解決方案】vscode 無法登錄遠程服務器的兩種解決辦法

解決方案一&#xff1a; 查找原因 命令 ps ajx | grep vscode 可能會看到一下這堆信息&#xff08;如果沒有大概率不是這個原因導致&#xff09; 這堆信息的含義&#xff1a;當你使用 vscode 遠程登錄服務器時&#xff0c;我們遠程機器服務端要給你啟動一個叫做 vscode serv…

一、對4*3按鍵模塊編程分析

一、4*3鍵盤模塊實物分析 說明&#xff1a; 1、橫著4排&#xff0c;豎著3列&#xff0c;加起來共7組&#xff0c;所以對外引出7根線。 2、根據排針終端引腳又可分兩類。即橫排和豎列對應的引腳。 二、代碼編寫構想&#xff1a; 1、使用7個gpio輸入中斷&#xff0c;檢測7個…

自然語言處理NLP入門 -- 第十節NLP 實戰項目 2: 簡單的聊天機器人

一、為什么要做聊天機器人&#xff1f; 在互聯網時代&#xff0c;我們日常接觸到的“在線客服”“自動問答”等&#xff0c;大多是以聊天機器人的形式出現。它能幫我們快速回復常見問題&#xff0c;讓用戶獲得及時的幫助&#xff0c;并在一定程度上減少人工客服的壓力。 同時&…

linux(1)文件管理

文章目錄 文件目錄系統相對路徑絕對路徑命令解析器文件管理 文件目錄系統 bin&#xff1a; 二進制文件目錄&#xff0c;存儲可執行文件 dev&#xff1a;設備目錄&#xff0c;所有的硬件都會抽象成文件存儲&#xff0c;比如鼠標鍵盤 home&#xff1a;存儲普通用戶的家目錄 li…

CSS—選擇器詳解:5分鐘動手掌握選擇器

個人博客&#xff1a;haichenyi.com。感謝關注 1. 目錄 1–目錄2–引言3–種類4–優先級 引言 什么是選擇器&#xff1f; CSS選擇器是CSS&#xff08;層疊樣式表&#xff09;中的一種規則&#xff0c;用于指定要應用樣式的HTML元素。它們就像是指向網頁中特定元素的指針&#…

大模型微調入門(Transformers + Pytorch)

目標 輸入&#xff1a;你是誰&#xff1f; 輸出&#xff1a;我們預訓練的名字。 訓練 為了性能好下載小參數模型&#xff0c;普通機器都能運行。 下載模型 # 方式1&#xff1a;使用魔搭社區SDK 下載 # down_deepseek.py from modelscope import snapshot_download model_…

DeepSeek實戰

DeepSeek 接入實戰&#xff1a;從零開始快速上手 引言 在當今的 AI 領域&#xff0c;DeepSeek 作為一個強大的自然語言處理&#xff08;NLP&#xff09;平臺&#xff0c;提供了豐富的 API 接口&#xff0c;幫助開發者快速實現智能對話、文本生成、語義分析等功能。本文將帶你…

Android NDK打包封裝教程與優化技巧

關于NDK打包封裝的問題。首先,用戶可能不太清楚NDK的基本概念,所以我應該先解釋NDK是什么以及它的作用。然后,用戶可能想知道如何在Android項目中使用NDK,所以需要分步驟說明配置過程,包括安裝NDK、配置CMake或ndk-build,創建JNI接口,編寫C/C++代碼,編譯和打包。 接下…

【告別雙日期面板!一招實現el-date-picker智能聯動日期選擇】

告別雙日期面板&#xff01;一招實現el-date-picker智能聯動日期選擇 1.需求背景2.DateTimePicker 現狀圖3.日期選擇器實現代碼4.日期選擇器實現效果圖5.日期時間選擇器實現代碼6.日期時間選擇器實現效果圖 1.需求背景 在用戶使用時間查詢時&#xff0c;我們經常需要按月份篩選…

Linux(ftrace)__mcount的實現原理

Linux 內核調試工具ftrace 之&#xff08;_mcount的實現原理&#xff09; ftrace 是 Linux 內核中的一種跟蹤工具&#xff0c;主要用于性能分析、調試和內核代碼的執行跟蹤。它通過在內核代碼的關鍵點插入探針&#xff08;probe&#xff09;來記錄函數調用和執行信息。這對于開…

Java注解(Annotation)

一、注解的定義 核心概念 注解是Java中一種特殊形式的“元數據”&#xff0c;用于為類、方法、字段、參數等代碼元素附加說明信息。它不會直接影響代碼邏輯&#xff0c;但可以通過編譯器、框架或反射機制進行解析和處理。 與注釋&#xff08;Comment&#xff09;的區別 注釋&a…

tauri2+typescript+vue+vite+leaflet等的簡單聯合使用(一)

項目目標 主要的目的是學習tauri。 流程 1、搭建項目 2、簡單的在項目使用leaflet 3、打包 準備項目 環境準備 廢話不多說&#xff0c;直接開始 需要有準備能運行Rust的環境和Node&#xff0c;對于Rust可以參考下面這位大佬的文章&#xff0c;Node不必細說。 Rust 和…

深入解析 Svelte:下一代前端框架的革命

深入解析 Svelte&#xff1a;下一代前端框架的革命 1. Svelte 簡介 Svelte 是一款前端框架&#xff0c;與 React、Vue 等傳統框架不同&#xff0c;它采用 編譯時&#xff08;Compile-time&#xff09; 方式來優化前端應用。它不像 React 或 Vue 依賴虛擬 DOM&#xff0c;而是…

關于流水線的理解

還是不太理解&#xff0c;我之前一直以為&#xff0c;對axis總線&#xff0c;每一級的寄存器就像fifo一樣&#xff0c;一級一級的分級存儲最后一級需要的數據。 像這張圖&#xff0c;一開始是在解析axis流形式的數據包&#xff0c;數據包一直都能輸入&#xff0c;所以valid一直…

Python代碼之美:從規范到藝術

基礎規范&#xff1a;代碼的"顏值"很重要 &#x1f449;大禮包&#x1f381;&#xff1a;&#x1f448; PEP 8&#xff1a;不只是規范&#xff0c;是寫作藝術 良好的代碼格式就像優美的書法&#xff0c;讓人賞心悅目。比如&#xff1a; # 不推薦的寫法 def calcul…

【AI+智造】在阿里云Ubuntu 24.04上部署DeepSeek R1 14B的完整方案

作者&#xff1a;Odoo技術開發/資深信息化負責人 日期&#xff1a;2025年2月28日 一、部署背景與目標 DeepSeek R1作為國產大語言模型的代表&#xff0c;憑借其強化學習驅動的推理能力&#xff0c;在復雜任務&#xff08;如數學問題、編程邏輯&#xff09;中表現優異。本地化部…

8 SpringBoot進階(上):AOP(面向切面編程技術)、AOP案例之統一操作日志

文章目錄 前言1. AOP基礎1.1 AOP概述: 什么是AOP?1.2 AOP快速入門1.3 Spring AOP核心中的相關術語(面試)2. AOP進階2.1 通知類型2.1.1 @Around:環繞通知,此注解標注的通知方法在目標方法前、后都被執行(通知的代碼在業務方法之前和之后都有)2.1.2 @Before:前置通知,此…