《Linux系統編程篇》System V信號量實現生產者與消費者問題(Linux 進程間通信(IPC))——基礎篇(拓展思維)

文章目錄

      • 📚 **生產者-消費者問題**
      • 🔑 **問題分析**
      • 🛠? **詳細實現:生產者-消費者**
        • **步驟 1:定義信號量和緩沖區**
        • **步驟 2:創建信號量**
        • **步驟 3:生產者進程**
        • **步驟 4:消費者進程**
        • **步驟 5:創建進程并啟動**
      • 🧑?🔧 **完整代碼示例**
      • 🎯 **關鍵點總結**

接上節,我們來詳細展開一下 生產者-消費者問題,并用 System V 信號量 來解決它。這個經典問題幫助我們理解如何在多個進程間同步和互斥地共享資源。

📚 生產者-消費者問題

生產者-消費者問題是多進程同步問題中的經典例子。問題的背景是:有兩個進程,一個生產者(Producer)不斷生產產品,另一個消費者(Consumer)不斷消費產品。兩者都需要共享一個有限的緩沖區。生產者往緩沖區寫入數據,消費者從緩沖區讀取數據。為了避免并發問題,我們需要同步生產者和消費者的訪問。

具體的挑戰是:

  1. 互斥:生產者和消費者在訪問共享緩沖區時,不能同時操作。
  2. 同步:緩沖區不能超過最大容量,也不能為空。

🔑 問題分析

我們需要使用信號量來解決這些問題,具體來說,我們需要:

  1. 一個信號量來控制緩沖區的空位置數(空位信號量)。
  2. 一個信號量來控制緩沖區的已滿位置數(已滿信號量)。
  3. 一個互斥信號量來保證每次只有一個進程(生產者或消費者)可以訪問緩沖區。

我們通過信號量來控制:

  • 當緩沖區為空時,消費者應該等待。
  • 當緩沖區已滿時,生產者應該等待。
  • 互斥信號量保證在訪問共享緩沖區時,只有一個進程能夠進入臨界區。

🛠? 詳細實現:生產者-消費者

步驟 1:定義信號量和緩沖區

我們將使用以下信號量:

  • empty:緩沖區中空位的數量,初始值為 BUFFER_SIZE
  • full:緩沖區中已滿的數量,初始值為 0
  • mutex:互斥鎖,用來確保每次只有一個進程能夠訪問緩沖區,初始值為 1

緩沖區本身可以用一個數組來表示:

#define BUFFER_SIZE 5  // 緩沖區大小
#define NUM_ITEMS 10   // 生產和消費的物品數量int buffer[BUFFER_SIZE];  // 緩沖區
int in = 0;  // 指向下一個要寫入的位置
int out = 0; // 指向下一個要讀取的位置
步驟 2:創建信號量

我們通過 semget() 創建信號量集:

int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);  // 創建3個信號量
if (sem_id == -1) {perror("semget");exit(1);
}// 初始化信號量
semctl(sem_id, 0, SETVAL, BUFFER_SIZE);  // empty 信號量:初始為緩沖區大小
semctl(sem_id, 1, SETVAL, 0);  // full 信號量:初始為0,表示緩沖區沒有物品
semctl(sem_id, 2, SETVAL, 1);  // mutex 信號量:初始為1,表示可以訪問緩沖區
步驟 3:生產者進程

生產者進程的工作流程如下:

  1. 等待空位信號量(empty:只有在有空位時才能生產。
  2. 獲取互斥信號量(mutex:進入臨界區,確保沒有其他進程操作緩沖區。
  3. 生產:將數據放入緩沖區。
  4. 釋放互斥信號量(mutex:退出臨界區。
  5. 增加已滿信號量(full:表明緩沖區中有一個新產品,消費者可以消費。

生產者代碼示例:

void producer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(empty)sops[0].sem_num = 0;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);buffer[in] = i;printf("生產者生產了產品 %d\n", i);in = (in + 1) % BUFFER_SIZE;// V(mutex) 和 V(full)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 1; // fullsops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}
步驟 4:消費者進程

消費者進程的工作流程如下:

  1. 等待已滿信號量(full:只有在緩沖區有物品時才能消費。
  2. 獲取互斥信號量(mutex:進入臨界區,確保沒有其他進程操作緩沖區。
  3. 消費:從緩沖區中取出數據。
  4. 釋放互斥信號量(mutex:退出臨界區。
  5. 增加空位信號量(empty:表明緩沖區有一個空位,生產者可以生產。

消費者代碼示例:

void consumer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(full)sops[0].sem_num = 1;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);int item = buffer[out];printf("消費者消費了產品 %d\n", item);out = (out + 1) % BUFFER_SIZE;// V(mutex) 和 V(empty)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 0; // emptysops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}

步驟 5:創建進程并啟動

main() 函數中,我們創建了一個子進程,用于運行消費者進程。父進程將作為生產者運行。

int main() {int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);if (sem_id == -1) {perror("semget");exit(1);}semctl(sem_id, 0, SETVAL, BUFFER_SIZE); // emptysemctl(sem_id, 1, SETVAL, 0);           // fullsemctl(sem_id, 2, SETVAL, 1);           // mutexpid_t pid = fork();if (pid == 0) {consumer(sem_id);} else if (pid > 0) {producer(sem_id);wait(NULL);semctl(sem_id, 0, IPC_RMID);} else {perror("fork failed");exit(1);}return 0;
}

在這段代碼中:

  • fork():通過 fork() 創建一個新的子進程。父進程作為生產者執行 producer() 函數,子進程作為消費者執行 consumer() 函數。
  • 父進程和子進程分工:生產者不斷生產物品放入緩沖區,消費者從緩沖區取出物品進行消費。
  • wait(NULL):父進程使用 wait() 來等待子進程的結束,這樣可以確保父進程在子進程完成后再退出,避免資源的提前釋放。
  • 刪除信號量集:為了避免信號量集泄露,程序結束時通過 semctl() 刪除創建的信號量集。

🧑?🔧 完整代碼示例

這里是完整的代碼,包含了生產者和消費者進程的實現,以及使用 System V 信號量同步和互斥訪問共享緩沖區。

#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define BUFFER_SIZE 5
#define NUM_ITEMS 10int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;void producer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(empty)sops[0].sem_num = 0;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);buffer[in] = i;printf("生產者生產了產品 %d\n", i);in = (in + 1) % BUFFER_SIZE;// V(mutex) 和 V(full)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 1; // fullsops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}void consumer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(full)sops[0].sem_num = 1;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);int item = buffer[out];printf("消費者消費了產品 %d\n", item);out = (out + 1) % BUFFER_SIZE;// V(mutex) 和 V(empty)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 0; // emptysops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}int main() {int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);if (sem_id == -1) {perror("semget");exit(1);}semctl(sem_id, 0, SETVAL, BUFFER_SIZE); // emptysemctl(sem_id, 1, SETVAL, 0);           // fullsemctl(sem_id, 2, SETVAL, 1);           // mutexpid_t pid = fork();if (pid == 0) {consumer(sem_id);} else if (pid > 0) {producer(sem_id);wait(NULL);semctl(sem_id, 0, IPC_RMID);} else {perror("fork failed");exit(1);}return 0;
}

🎯 關鍵點總結

  1. 信號量的使用:通過 emptyfullmutex 信號量實現生產者和消費者的同步與互斥。
  2. semop() 調用:每次生產者或消費者對共享資源進行操作時,都需要通過 semop() 來執行信號量操作,確保數據的正確訪問順序。
  3. P()V() 操作:通過 P() 操作來阻塞等待資源,V() 操作來釋放資源,確保進程按預期順序執行。

通過這個實例,你可以更加深入地理解如何使用 System V 信號量 來解決實際的同步和互斥問題。在實際應用中,生產者消費者模式廣泛應用于操作系統調度、緩沖區管理等場景。

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

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

相關文章

利用 Python 爬蟲進行跨境電商數據采集

1 引言2 代理IP的優勢3 獲取代理IP賬號4 爬取實戰案例---&#xff08;某電商網站爬取&#xff09;4.1 網站分析4.2 編寫代碼4.3 優化代碼 5 總結 1 引言 在數字化時代&#xff0c;數據作為核心資源蘊含重要價值&#xff0c;網絡爬蟲成為企業洞察市場趨勢、學術研究探索未知領域…

HONOR榮耀MagicBook 15 2021款 獨顯(BOD-WXX9,BDR-WFH9HN)原廠Win10系統

適用型號&#xff1a;【BOD-WXX9】 MagicBook 15 2021款 i7 獨顯 MX450 16GB512GB (BDR-WFE9HN) MagicBook 15 2021款 i5 獨顯 MX450 16GB512GB (BDR-WFH9HN) MagicBook 15 2021款 i5 集顯 16GB512GB (BDR-WFH9HN) 鏈接&#xff1a;https://pan.baidu.com/s/1S6L57ADS18fnJZ1…

c語言實現三子棋小游戲(涉及二維數組、函數、循環、常量、動態取地址等知識點)

使用C語言實現一個三子棋小游戲 涉及知識點&#xff1a;二維數組、自定義函數、自帶函數庫、循環、常量、動態取地址等等 一些細節點&#xff1a; 1、引入自定義頭文件&#xff0c;需要用""雙引號包裹文件名&#xff0c;目的是為了和官方頭文件的<>區分開。…

C語言數據類型及其使用 (帶示例)

目錄 1. 基本數據類型 整型 浮點型 字符型 2. 構造數據類型 數組 結構體 聯合體&#xff08;共用體&#xff09; 枚舉類型 3. 指針類型 4. 空類型 在 C 語言中&#xff0c;數據類型是非常重要的概念&#xff0c;它決定了數據在內存中的存儲方式、占用空間大小以及可…

Web自動化之Selenium添加網站Cookies實現免登錄

在使用Selenium進行Web自動化時&#xff0c;添加網站Cookies是實現免登錄的一種高效方法。通過模擬瀏覽器行為&#xff0c;我們可以將已登錄狀態的Cookies存儲起來&#xff0c;并在下次自動化測試或爬蟲任務中直接加載這些Cookies&#xff0c;從而跳過登錄步驟。 Cookies簡介 …

NAT 技術:網絡中的 “地址魔術師”

目錄 一、性能瓶頸&#xff1a;NAT 的 “阿喀琉斯之踵” &#xff08;一&#xff09;數據包處理延遲 &#xff08;二&#xff09;高并發下的性能損耗 二、應用兼容性&#xff1a;NAT 帶來的 “適配難題” &#xff08;一&#xff09;端到端通信的困境 &#xff08;二&…

php序列化與反序列化

文章目錄 基礎知識魔術方法&#xff1a;在序列化和反序列化過程中自動調用的方法什么是 __destruct() 方法&#xff1f;何時觸發 __destruct() 方法&#xff1f;用途&#xff1a;語法示例&#xff1a; 反序列化漏洞利用前提條件一些繞過策略繞過__wakeup函數繞過正則匹配繞過相…

docker 占用系統空間太大了,整體遷移到掛載的其他磁盤|【當前普通用戶使用docker時,無法指定鏡像、容器安裝位置【無法指定】】

文章目錄 前言【核心步驟皆為 大模型生成的方案】總結步驟應該是&#xff1a;詳細步驟如下1. **停止 Docker 服務**2. **備份原數據&#xff08;防止遷移失敗&#xff09;**3. **遷移數據到新磁盤**4. **修改 Docker 配置文件**5. **重啟 Docker 服務**6. **驗證容器和鏡像**7.…

設計后端返回給前端的返回體

目錄 1、為什么要設計返回體&#xff1f; 2、返回體包含哪些內容&#xff08;如何設計&#xff09;&#xff1f; 舉例 3、總結 1、為什么要設計返回體&#xff1f; 在設計后端返回給前端的返回體時&#xff0c;通常需要遵循一定的規范&#xff0c;以確保前后端交互的清晰性…

Springboot 自動化裝配的原理

Springboot 自動化裝配的原理 SpringBoot 主要作用為&#xff1a;起步依賴、自動裝配。而為了實現這種功能&#xff0c;SpringBoot 底層主要使用了 SpringBootApplication 注解。 首先&#xff0c;SpringBootApplication 是一個復合注解&#xff0c;它結合了 Configuration、…

基于vue框架的游戲博客網站設計iw282(程序+源碼+數據庫+調試部署+開發環境)帶論文文檔1萬字以上,文末可獲取,系統界面在最后面。

系統程序文件列表 項目功能&#xff1a;用戶,博客信息,資源共享,游戲視頻,游戲照片 開題報告內容 基于FlaskVue框架的游戲博客網站設計開題報告 一、項目背景與意義 隨著互聯網技術的飛速發展和游戲產業的不斷壯大&#xff0c;游戲玩家對游戲資訊、攻略、評測等內容的需求日…

算法-二叉樹篇13-路徑總和

路徑總和 力扣題目鏈接 題目描述 給你二叉樹的根節點 root 和一個表示目標和的整數 targetSum 。判斷該樹中是否存在 根節點到葉子節點 的路徑&#xff0c;這條路徑上所有節點值相加等于目標和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否則&#xff0c;返回…

8. 示例:對32位數據總線實現位寬和值域覆蓋

文章目錄 前言示例一&#xff1a;示例二&#xff1a;示例三&#xff1a;仿真與覆蓋率分析覆蓋點詳細說明覆蓋率提升技巧常見錯誤排查 示例四&#xff1a;仿真步驟 前言 針對32位數據總線實現位寬和值域的覆蓋&#xff0c;并且能夠用xrun運行&#xff0c;查看日志和波形。cover…

TDengine 中的數據庫

數據庫概念 時序數據庫 TDengine 中數據庫概念&#xff0c;等同于關系型數據庫 MYSQL PostgreSQL 中的數據庫&#xff0c;都是對資源進行分割管理的單位。 TDengine 數據庫與關系型數據庫最大區別是跨庫操作&#xff0c;TDengine 數據庫跨庫操作除了少量幾個SQL 能支持外&…

開源電商項目、物聯網項目、銷售系統項目和社區團購項目

以下是推薦的開源電商項目、物聯網項目、銷售系統項目和社區團購項目&#xff0c;均使用Java開發&#xff0c;且無需付費&#xff0c;GitHub地址如下&#xff1a; ### 開源電商項目 1. **mall** GitHub地址&#xff1a;[https://github.com/macrozheng/mall](https://git…

如何設計一個短鏈系統?

短鏈系統設計的關鍵要點: 系統功能實現 短鏈生成:接收長鏈接,先檢查是否已有對應短鏈,存在則直接返回。否則,使用分布式 ID 生成器(如號段模式、SnowFlake 算法、數據庫自增 ID、Redis 自增等)生成唯一 ID,或通過哈希算法(如 MurmurHash)處理長鏈接得到哈希值。再將生…

數據結構(初階)(三)----單鏈表

單鏈表 概念 概念&#xff1a;鏈表是?種物理存儲結構上?連續、?順序的存儲結構&#xff0c;數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。 結點 與順序表不同的是&#xff0c;鏈表的結構類似于帶車頭的火車車廂&#xff0c;&#xff0c;鏈表的每個車廂都是獨立…

游戲引擎學習第129天

倉庫:https://gitee.com/mrxiao_com/2d_game_3 小妙招: vscode:定位錯誤行 一頓狂按F8 重構快捷鍵:F2 重構相關的變量 回顧并為今天的內容做準備 今天的工作主要集中在渲染器的改進上&#xff0c;渲染器現在運行得相當不錯&#xff0c;得益于一些優化和組織上的改進。我們計…

文字描邊實現內黃外綠效果

網頁使用 <!DOCTYPE html> <html> <head> <style> .text-effect {color: #ffd700; /* 黃色文字 */-webkit-text-stroke: 2px #008000; /* 綠色描邊&#xff08;兼容Webkit內核&#xff09; */text-stroke: 2px #008000; /* 標準語法 *…

yolov8 目標追蹤 (源碼 +效果圖)

1.在代碼中 增加了s鍵開始追蹤 e鍵結束追蹤 顯示移動距離(代碼中可調標尺和像素的比值 以便接近實際距離) 2.繪制了監測區域 只在區域內的檢測 3.規定了檢測的類別 只有人類才繪制軌跡 import osimport cv2 from ultralytics import YOLO from collections import defaultdic…