Linux——進程間通信,匿名管道,進程池

文章目錄

  • 一、進程間通信(IPC)的理解
    • 1.為什么進程間要通信(IPC)
    • 2.如何進行通信
  • 二、匿名管道
    • 1.管道的理解
    • 2.匿名管道的使用
    • 3.管道的五種特性
    • 4.管道的四種通信情況
    • 5.管道緩沖區容量
  • 三、進程池
    • 1.進程池的理解
    • 2.進程池的制作
  • 四、源碼
    • ProcessPool.hpp
    • Task.hpp
    • Main.cc

一、進程間通信(IPC)的理解

1.為什么進程間要通信(IPC)

首先進程之間是相互獨立的,盡管是父子進程之間,它們雖然資源共享,但當子進程需要修改數據時仍然需要 進行寫時拷貝,保持獨立性。

而讓進程間通信可以實現數據之間的交互,資源共享,事件通知,又或者是讓一個進程對另一個進程進行控制。

進程間通信是操作系統中實現進程間協作和數據交換的重要機制 ,它使得多個進程能夠共同完成任務,提高系統的效率和可靠性。

2.如何進行通信

進程間通信的原理其實很簡單,只需要兩個進程共同訪問一個資源,而一個進程對資源的更改能被另一進程感知到,從而做出相應的操作。

所以通信的前提是進程之間能夠訪問同一個資源,而且該資源是公共的,而不是某進程內部的。

IPC 的典型方式對比

在這里插入圖片描述

二、匿名管道

1.管道的理解

我們把進程之間通信的介質(資源)叫作管道。
開發者在設計管道技術時文件系統已經比較成熟,所以為了方便管理該資源就使用文件來實現, 而對文件的讀寫就是通信的過程 ,但它與一般的文件還是有些區別,文件都是儲存到磁盤上的,而進程之間通信用的文件并不需要把它儲存到磁盤上,它只是作為一個傳輸介質。

它比較特殊,所以起名為管道。管道其實是一個內存級的文件。

在這里插入圖片描述

注意:父子進程之間的管道叫作匿名管道,顧名思義就是沒有名字,也不需要名字,因為子進程能夠繼承下來父進程開辟的管道資源。

2.匿名管道的使用

創建匿名管道常用的接口是:

            int pipe(int pipefd[2]);

需要包含頭文件:

            #include<unistd.h>
  • 返回值:創建成功返回0,失敗返回-1
  • 參數:這個是一個輸出型參數,傳入一個int類型長度為2的數組,然后得到
    pipefd[0]:以讀的方式打開的文件描述符
    pipefd[1]:以寫的方式打開的文件描述符。

示例:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int main()
{int pipefd[2];pipe(pipefd);int rfd = pipefd[0],wfd = pipefd[1];pid_t id = fork();if(id == 0){close(wfd);//關閉子進程的寫文件,只讓它讀int k=0;while(true){read(rfd,&k,sizeof(k));printf("read:%d\n",k);}}else{close(rfd);//關閉父進程的讀文件,只讓它寫。int num=0;while(true){write(wfd,&num,sizeof(num));num++;sleep(1);}}return 0;
}

要記住pipefd[2]中哪個是讀哪個是寫有一個小技巧,0像嘴巴,所以下標為0的是讀,1像鋼筆,所以1下標是寫。

3.管道的五種特性

  1. 匿名管道,只能用來進行具有血緣關系的進程間通信(用于父與子)。
  2. 管道文件,自帶同步機制。如上代碼示例,父進程寫一次休眠一秒,而子進程是一直不斷地讀,快的一端會遷就于慢的一端,最后實現同步。
  3. 管道是面向字節流的。怎么讀與怎么寫并沒有聯系,比如寫入“hello world”,但可能讀到“hel”,這取決于你要讀多少字節。
  4. 管道是單向通信的。也就是a(表示進程)寫的時候b讀。b寫的時候a在讀。而不是既在寫同時也在讀。
  5. 管道(文件)的生命周期是隨進程的。進程結束管道也隨之銷毀。

4.管道的四種通信情況

  • 寫慢,讀快 — 讀端就要阻塞(等待寫端寫入)。
  • 寫快,讀慢 —到管道容量滿了后,寫端就要阻塞(等待讀端讀取數據,然后就可以覆蓋式地繼續往管道寫入)。
  • 寫關閉,讀繼續 — read就會返回0,表示文件結尾。
  • 寫繼續,讀關閉 — 寫端不再有意義,系統會殺掉寫端進程。

5.管道緩沖區容量

管道緩沖區容量為64kb,大家可以根據管道的性質與通信特點,自行進行測試。

三、進程池

1.進程池的理解

在程序使用內存的時候,比如vector擴容機制,會提前給你開辟一塊空間供你使用,盡管現在用不到,相當于做一下預備。減少開辟空間的頻次,從而達到提高效率的效果。

那么進程池也同樣,給父進程提前開辟一些子進程,提供父進程使用。其中我們使用匿名管道建立聯系。
在這里插入圖片描述
在父進程給子進程派發任務時,為了提高效率會讓每個子進程均勻地分配到任務(稱為負載均勻),而不是把大部分的任務都派發到一個子進程上,通常會有以下策略:

  • 輪詢:按順序一一分配。
  • 隨機:隨機進行分配。
  • 負載因子:設計一個負載因子,讓子進程按負載因子的大小排成一個小根堆,每次取出堆頭的子進程派發任務,然后更新負載因子插回到堆中。

2.進程池的制作

在面向對象的編程中最重要的就是對對象的描述與組織,這里我們的核心就是對管道進行管理。那么首先需要一個類對管道進行描述。

class Channel
{
public:Channel(int fd,pid_t id): _wfd(fd),_subid(id){ }//... ...~Channel(){}
private:int _wfd;int _subid;
};

_wfd是該管道對應寫端的fd,_subid是該管道對應的子進程的pid。

這里我們不必把rfd(讀端fd)加入,因為我們現在對管道的描述組織,目的是方便父進程管理,而rfd是給子進程用的,所以不用添加為變量。

這里我們就以輪詢的方式派發任務,剛才創建的Channel相當于對管道的描述,接下來創建ChannelManage進行組織。這里選擇使用數組來管理,派發任務方式選擇輪詢,所以需要記錄下一個需要派發到的管道的下標。

class ChannelManage
{
public:ChannelManage():_next(0){}//... ...~ChannelManage(){}
private:vector<Channel> _channels;int _next;
};

接下來還需要創建一個類對整體的進程池做管理。

class ProcessPool
{
public:ProcessPool(int num) : __process_num(num){}// ... ...~ProcessPool(){}
private:ChannelManage _cm;int _process_num;
};

其中_process_num表示需要創建多少子進程,這是由使用者來決定的。

在ProcessPool中我們準備實現這些方法

  • bool Start():用于創建子進程。
    由于我們是要生成多個通道所以需要循環來進行,而單趟循環需要做以下這些操作:

    1.創建管道,然后創建子進程。(這樣能讓子進程繼承到管道信息)

    2.關于子進程:寫端關閉,然后執行Work(),最后把讀端關閉,并exit退出。

    3.關于父進程:讀端關閉,然后把wfd,pid存入_cm中。

  • void Work(int rfd):用于子進程讀取任務碼并執行命令。

  • void Run():用于獲取并派發任務。

  • void Stop():用于關閉寫端并回收子進程。

最后為方便測試我們還需要一個管理任務的類和方法。我們可以單獨創建一個Task.hpp文件。

typedef void (*task_t)();class TaskManage
{
public:TaskManage(){   //隨機數種子srand((unsigned int)time(nullptr));}int Code(){   //隨機生成任務碼(數組下標)return rand()%_tasks.size();}void Execute(int code){   //執行任務_tasks[code]();}// ... ...~TaskManage(){}
private:vector<function<task_t>> _tasks;//用于儲存任務的數組
};

然后需要在ProcessPool中放入TaskManage成員變量,并在ProcessPool的構造函數中完成對_tasks中內容的插入。具體操作參考下面源碼。

四、源碼

ProcessPool.hpp

#ifndef _PROCESS_POOL_HPP_
#define _PROCESS_POOL_HPP_#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;//先描述
class Channel
{
public:Channel(int fd,pid_t id): _wfd(fd),_subid(id){_name = "channel-" + to_string(_wfd) + "-" + to_string(_subid);}~Channel(){}void Send(int code){int n = write(_wfd,&code,sizeof(code));(void)n;}void Close(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subid, nullptr, 0);(void)rid;}int Fd(){return _wfd;}pid_t SubId(){return _subid;}string Name(){return _name;}private:int _wfd;pid_t _subid;string _name;//int _loadnum;
};//再組織
class ChannelManager
{
public:ChannelManager(): _next(0){}~ChannelManager(){}void Insert(int wfd,pid_t subid){_channels.emplace_back(wfd,subid);// Channel c(wfd,subid);// _channels.push_back(move(c));}Channel& Select(){auto& c = _channels[_next];_next++;_next %= _channels.size();return c;}void PrintChannel(){for(auto& channel : _channels){cout << channel.Name() << endl;}}void CloseAll(){for(auto& channel: _channels){channel.Close();}}void StopSubProcess(){for(auto& channel: _channels){channel.Close();cout << "關閉: " << channel.Name() << endl;}}void WaitSubProcess(){for(auto& channel: _channels){channel.Wait();cout << "回收: " << channel.Name() << endl;}}void CloseAndWait(){for(auto& channel: _channels){channel.Close();cout << "關閉: " << channel.Name() << endl;channel.Wait();cout << "回收: " << channel.Name() << endl;}//解決方法1 倒著關閉// for(int i = _channels.size() - 1;i >= 0;i--)// {//     _channels[i].Close();//     cout << "關閉: " << _channels[i].Name() << endl;//     _channels[i].Wait();//     cout << "回收: " << _channels[i].Name() << endl;// }}private:vector<Channel> _channels;int _next;
};const int gdefaultnum = 5;class ProcessPool
{
public:ProcessPool(int num): _process_num(num){_tm.Register(PrintLog);_tm.Register(DownLoad);_tm.Register(UpLoad);}~ProcessPool(){}void Work(int rfd){while(true){int code = 0;size_t n = read(rfd,&code,sizeof(code));if(n > 0){if(n != sizeof(code)){continue;}cout << "子進程[" << getpid() << "]收到一個任務碼: " << code << endl;_tm.Execute(code);}else if(n == 0){cout << "子進程退出" << endl;break;}else{cout << "讀取錯誤" << endl;break;}}}bool Start(){for(int i = 0;i < _process_num;i++){//1.創建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if(n < 0){return false;}//2.創建子進程pid_t subid = fork();if(subid < 0){return false;}else if(subid == 0){//子進程//關閉子進程繼承的哥哥的w端_cm.CloseAll();//3.關閉不需要的文件描述符close(pipefd[1]);Work(pipefd[0]);close(pipefd[0]);exit(0);}else{//父進程//3.關閉不需要的文件描述符close(pipefd[0]);_cm.Insert(pipefd[1],subid);}}return true;}void Debug(){_cm.PrintChannel();}void Run(){//1.選擇一個任務int taskcode = _tm.Code();//2.選擇一個信道[子進程],負載均衡的選擇一個子進程,完成任務auto& c = _cm.Select();cout << "選擇了一個子進程: " << c.Name() << endl;//3.發送任務c.Send(taskcode);cout << "發送了一個任務碼: " << taskcode << endl;}void Stop(){//關閉父進程//_cm.StopSubProcess();//回收所有的子進程//_cm.WaitSubProcess();_cm.CloseAndWait();}private:ChannelManager _cm;int _process_num;TaskManager _tm;
};#endif

Task.hpp

#pragma once#include <iostream>
#include <vector>
#include <ctime>
using namespace std;typedef void (*task_t)();void PrintLog()
{cout << "我是一個打印日志的任務" << endl;
}void DownLoad()
{cout << "我是一個下載的任務" << endl;
}void UpLoad()
{cout << "我是一個上傳的任務" << endl;
}class TaskManager
{
public:TaskManager(){srand((unsigned int)time(nullptr));}~TaskManager(){}void Register(task_t t){_tasks.push_back(t);}int Code(){return rand() % _tasks.size();}void Execute(int code){if(code >= 0 && code < _tasks.size()){_tasks[code]();}}private:vector<task_t> _tasks;
};

Main.cc

#include "ProcessPool.hpp"int main()
{//創建進程池對象ProcessPool pp(gdefaultnum);//啟動進程池pp.Start();//自動派發任務int cnt = 10;while(cnt--){pp.Run();sleep(1);}//回收,結束進程池pp.Stop();return 0;
}

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

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

相關文章

深度分析Java內存回收機制

內存回收機制是Java區別于C/C等語言的核心特性之一&#xff0c;也是Java開發者理解程序性能、解決內存相關問題&#xff08;如內存泄漏、OOM&#xff09;的關鍵。 核心目標&#xff1a; 自動回收程序中不再使用的對象所占用的內存&#xff0c;防止內存耗盡&#xff0c;同時盡量…

uniapp “requestPayment:fail [payment支付寶:62009]未知錯誤“

解決方案&#xff1a;兄弟&#xff0c;有一種可能是你用測試機沒有安裝支付寶

分布在內側內嗅皮層(MEC)的帶狀細胞對NLP中的深層語義分析的積極影響和啟示

帶狀細胞&#xff08;Band Cells&#xff09;作為內側內嗅皮層&#xff08;Medial Entorhinal Cortex, MEC&#xff09;層Ⅱ/Ⅲ的核心空間編碼單元&#xff08;如網格細胞、頭方向細胞等&#xff09;&#xff0c;其獨特的神經計算機制為自然語言處理&#xff08;NLP&#xff09…

綜合實驗(4)

文章目錄 目錄 文章目錄 前言 實驗配置 實驗總結 總結 前言 Cisco IOS Site-to-Site VPN&#xff08;虛擬專用網絡&#xff09;是一種通過公共網絡&#xff08;如互聯網&#xff09;建立安全連接的技術&#xff0c;使不同地理位置的局域網&#xff08;LAN&#xff09;能夠安…

JavaSE:開發環境的搭建(Eclipse)

一、IDE概述與核心價值 集成開發環境定義 提供編譯器、調試器、項目管理工具的統一平臺&#xff0c;顯著提升開發效率。 Eclipse核心優勢&#xff1a; 免費開源 &#xff1a;社區驅動&#xff0c;無授權費用跨平臺支持 &#xff1a;Windows/Linux/macOS全兼容多語言擴展 &a…

使用LLaMA-Factory對大模型進行微調

之前了解過一些LLM從訓練到落地的過程; 其中一個重要的步驟就是微調; 預訓練&#xff1a;在大規模數據上學習通用語言知識。(使用海量無標注文本&#xff08;TB級&#xff09;) 微調&#xff1a;在預訓練基礎上&#xff0c;使用特定任務的標注數據進一步優化模型。(使用少量任務…

WxPython——一些最常見的錯誤現象及解決方法

一些最常見的錯誤現象及解決方法 有一些錯誤它們可能會發生在你的wxPython應用程序對象或初始的頂級窗口在創建時&#xff0c;這些錯誤可能是很難診斷的。下面我們列出一些最常見的錯誤現象及解決方法&#xff1a; 錯誤現象&#xff1a;程序啟動時提示“unable to import modul…

SparkSQL 子查詢 IN/NOT IN 對 NULL 值的處理

SparkSQL 子查詢 IN/NOT IN 對 NULL 值的處理 官網&#xff1a;https://spark.apache.org/docs/4.0.0/sql-ref-functions.html https://spark.apache.org/docs/4.0.0/sql-ref-null-semantics.html#innot-in-subquery Unlike the EXISTS expression, IN expression can return…

【安卓筆記】lifecycle與viewModel

0. 環境&#xff1a; 電腦&#xff1a;Windows10 Android Studio: 2024.3.2 編程語言: Java Gradle version&#xff1a;8.11.1 Compile Sdk Version&#xff1a;35 Java 版本&#xff1a;Java11 1. 本篇文章涉及到的內容 lifecycle livedata databinding viewModel 2. …

84、逆向工程開發方法

逆向工程開發方法是一種通過分析現有產品、系統或代碼來理解其設計原理、功能實現及潛在缺陷&#xff0c;并在此基礎上進行改進、復制或創新的技術過程。它廣泛應用于軟件、硬件、機械、電子等多個領域&#xff0c;尤其在缺乏原始設計文檔或需要快速掌握復雜系統時具有顯著優勢…

ospf單區域實驗

拓撲圖&#xff1a;AR1&#xff1a;[Huawei]ospf 1 router-id 1.1.1.1 [Huawei-ospf-1]area 0[Huawei-ospf-1-area-0.0.0.0]network 192.168.1.0 0.0.0.255&#xff08;1.當前網段會被ospf的進程1學習到然后通告出去&#xff1b;2.如果接口的IP地址處于這個網段中&#xff0c…

Linux命令基礎完結篇

用戶權限修改 chmod修改文件權限 文字設定法 u&#xff1a;所有者g&#xff1a;所屬組o&#xff1a;其他人a&#xff1a;所有&#xff1a;添加權限-&#xff1a;刪除權限&#xff1a;賦予權限數字設定法 r&#xff1a;4w&#xff1a;2x&#xff1a;1每一組權限&#xff1a;0~7舉…

高效互聯,ModbusTCP轉EtherCAT網關賦能新能源電纜智能制造

在新能源汽車快速發展的背景下&#xff0c;新能源電纜作為關鍵組件&#xff0c;需滿足耐高低溫、阻燃、耐老化等嚴苛要求&#xff0c;這對生產線的工藝與設備提出了更高標準。為提升制造效率&#xff0c;某領先設備制造商創新采用**ModbusTCP轉EtherCAT網關**技術&#xff0c;實…

Java_多線程_生產者消費者模型_互斥鎖,阻塞隊列

生產者消費者模型(Producer-Consumer Model)是計算機科學中一個經典的并發編程模型&#xff0c;用于解決多線程/多進程環境下的協作問題。 基本概念 生產者&#xff1a;負責生成數據或任務的實體 消費者&#xff1a;負責處理數據或執行任務的實體 緩沖區&#xff1a;生產者與消…

Vue3實現視頻播放彈窗組件,支持全屏播放,音量控制,進度條自定義樣式,適配瀏覽器小窗播放,視頻大小自適配,緩沖loading,代碼復制即用

效果圖組件所需VUE3代碼<template><div class"video-dialog" :class"fullScreen && video-dialog-full-screen"><el-dialogv-model"props.visible"draggable:show-close"false"title""centeralign-c…

LLM層歸一化:γβ與均值方差的協同奧秘

LLM層歸一化參數均值和方差;縮放和平移參數是什么 層歸一化(Layer Normalization,LN)是深度學習中用于穩定神經網絡訓練的一種歸一化技術 均值和方差參數用于對輸入數據進行標準化處理,即將輸入數據轉換為均值為0、方差為1的標準正態分布 縮放因子γ\gammaγ:標準化后…

智慧場景:定制開發開源AI智能名片S2B2C商城小程序賦能零售新體驗

摘要&#xff1a;智慧場景作為零售行業創新發展的關鍵載體&#xff0c;正深刻改變著消費者的生活方式。本文聚焦智慧零售模式下智慧場景的構建&#xff0c;以定制開發開源AI智能名片S2B2C商城小程序為切入點&#xff0c;深入探討其在零售企業選址布局、商業模式創新、經營理念轉…

QML WorkerScript

WorkerScript是QML中實現多線程編程的關鍵組件&#xff0c;它允許開發者將耗時操作移至后臺線程執行&#xff0c;避免阻塞主UI線程&#xff0c;從而提升應用響應速度和用戶體驗。本文將全面介紹WorkerScript的核心機制、使用方法和最佳實踐。WorkerScript核心機制WorkerScript通…

銳浪報表 Grid++Report 表頭表尾的隱藏

設計銳浪表格的模板時&#xff0c;可以通過設計多個表頭、表尾&#xff0c;表頭、表尾中放入打印控件&#xff0c;可以打印相關的數據。在真實打印時&#xff0c;可以通過打印時讓表頭、表尾隱藏或顯示&#xff0c;實現用戶的表格樣式。一、表頭的指定1、 表頭可以多個&#xf…

低速信號設計之 QSPI 篇

一、引言? 在服務器技術不斷演進的當下,對高效、穩定的數據存儲和傳輸需求日益增長。QSPI(Quad Serial Peripheral Interface)總線作為一種高速、串行的外圍設備接口,在服務器領域中發揮著關鍵作用。它為服務器中的各類存儲設備及部分外圍芯片與主處理器之間提供了快速可…