進程間通信--匿名管道

進程間通信介紹

?進程間通信目的

  • 數據傳輸:一個進程需要將它的數據發送給另一個進程
  • 資源共享:多個進程之間共享同樣的資源。
  • 通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
  • 進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態改變。

?管道


什么是管道
管道是Unix中最古老的進程間通信的形式。我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道”如:

?用fork來共享管道原理

?站在文件描述符角度-深度理解管道 (內存級)

?左邊是進程管理,右邊是文件管理,進程通過全局變量能找到文件描述符標,也就找到了對應的file文件也能打開磁盤上的文件拷貝到緩沖區里進行讀寫,父進程創建子進程,發生寫實拷貝,類似于淺拷貝,此時不做文件操作,文件描述符里面的對于關系和父進程一樣,struc_file沒有關閉,因為父進程還在,?struc_file的對應的引用計數不為0就一定不會關閉,既然訪問的同一個struc_file,也可以同時訪問其中的file文件,通過緩沖區可以進行文件的讀寫

  1. 父進程通過讀寫兩種方式打開內存級的文件返回給上層完成管道的創建
  2. 子進程繼承父進程的文件描述符表,發生淺拷貝,也能拿到父進程以讀寫打開的管道文件
  3. 父子都看得到,讓父子單向通信,父進程寫,子進程讀,各自關閉掉自己不需要的文件描述符

這個是管道是OS單獨設計的,得配上單獨的系統調用:pipe 內存級的,不需要文件路徑,沒有文件名,所以叫匿名管道,那我們怎么保證,兩個進程打開的是同一個管道的?

子進程繼承了父進程的文件描述符表

站在內核角度-管道本質

?匿名管道

#include <unistd.h>
功能:創建??名管道
原型
int pipe(int fd[2]);
參數
fd:?件描述符數組,其中fd[0]表?讀端, fd[1]表?寫端
返回值:成功返回0,失敗返回錯誤代碼
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{int fds[2]={0};int n=pipe(fds);if(n<0){cerr<<"Pipe error"<<endl;return 1;}cout<<"fds[0]"<<fds[0]<<endl;cout<<"fds[1]"<<fds[1]<<endl;return 0;
}

0,1,2被三個標準占用了,從3,4開始

父寫子讀,實現通信

#include<iostream>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<cstring>
using namespace std;void ChildWrite(int fd)
{
char buffer[1024];
int cnt=0;
while(true)
{
snprintf(buffer,sizeof(buffer),"I am child,pid:%d cnt:%d\n",getpid(),cnt++); 
write(fd,buffer,strlen(buffer));
sleep(1);
}
}
void FatherRead(int fd)
{char buffer[1024];while(true){buffer[0]=0;ssize_t n=read(fd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;cout<<"child says: "<<buffer<<endl;}}
}
int main()
{  //1.創建管道int fds[2]={0}; //fds[0]讀端,fds[1]寫端int n=pipe(fds);if(n<0){cerr<<"Pipe error"<<endl;return 1;}cout<<"fds[0]"<<fds[0]<<endl;cout<<"fds[1]"<<fds[1]<<endl;//3.創建子進程 f->r c->wpid_t id=fork();if(id==0){//childclose(fds[0]); //關閉讀端ChildWrite(fds[1]);close(fds[1]);exit(0);}
close(fds[1]); //關閉寫端
FatherRead(fds[0]);
waitpid(id,nullptr,0);
close(fds[0]);return 0;
}

父進程寫的cnt在不斷變化,子進程能讀到,說明實現了管道通信

五種特性

1.匿名管道,只能用來進行具有血緣關系的進程進行進程間通信(常用父與子,如上述代碼)

2.管道文件,自帶同步機制(父子進程進行IO同時進行,一個讀一個寫,不管是父快還是子快,父不斷寫,子read讀不到會阻塞住直到讀到為止)

3.管道是面向字節流的

4.管道是單向通信的(要么父寫子讀,要么子寫父讀)

5.?(管道)文件的生命周期隨進程

4種通信情況?

1.寫慢,讀快------>讀端阻塞等待寫端(進程)

2.寫快,讀慢------>緩沖區寫滿了,寫要阻塞等待讀端

3.寫關,讀開------>read會讀到返回值0,表示文件結尾

//寫一條就關
void ChildWrite(int fd)
{
char buffer[1024];
int cnt=0;
while(true)
{
snprintf(buffer,sizeof(buffer),"I am child,pid:%d cnt:%d\n",getpid(),cnt++); 
write(fd,buffer,strlen(buffer));
sleep(1);
break;
}
}
//觀察n的返回值
void FatherRead(int fd)
{char buffer[1024];while(true){buffer[0]=0;ssize_t n=read(fd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;cout<<"child says: "<<buffer<<endl;}else{cout<< "n:"<<n<<endl;}}
}

?4.讀關,寫開------->寫端再寫沒有意義,OS會殺掉寫端進程

#include<iostream>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<cstring>
using namespace std;void ChildWrite(int fd)
{
char buffer[1024];
int cnt=0;
while(true)
{
snprintf(buffer,sizeof(buffer),"I am child,pid:%d cnt:%d\n",getpid(),cnt++); 
write(fd,buffer,strlen(buffer));
sleep(1);
}
}
void FatherRead(int fd)
{char buffer[1024];while(true){buffer[0]=0;ssize_t n=read(fd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;cout<<"child says: "<<buffer<<endl;}else{cout<< "n:"<<n<<endl;sleep(4);}break;}
}
int main()
{  //1.創建管道int fds[2]={0}; //fds[0]讀端,fds[1]寫端int n=pipe(fds);if(n<0){cerr<<"Pipe error"<<endl;return 1;} cout<<"fds[0]"<<fds[0]<<endl;cout<<"fds[1]"<<fds[1]<<endl;//3.創建子進程 f->r c->wpid_t id=fork();if(id==0){//childclose(fds[0]); //關閉讀端ChildWrite(fds[1]);close(fds[1]);exit(0);}
close(fds[1]); //關閉寫端
FatherRead(fds[0]);
close(fds[0]);int status;
int ret=waitpid(id,&status,0);
//獲取到子進程的退出狀態
if(ret>0)
{printf("child code: %d exited  status: %d\n",(status)>>8&0xff,(status)&0x7f);
}
return 0;
}

發送異常信號13 SIGPIPE?

?基于匿名管道----進程池

父進程創建多個子進程,用匿名管道分派任務

?1.構建進程鏈,為進程池做準備

#ifndef __PROCESS__POOL_HPP__
#define __PROCESS__POOL_HPP__
#include <iostream>
#include <vector>
#include <unistd.h>
#include<cstdlib>
using namespace std;
const int gdefaultnum = 5; // 要創建幾個進程
// 先描述 單個進程
class Channel
{
public:Channel() {}~Channel() {}private:int _wfd;
};// 在組織  進程鏈
class ChannelManager
{
public:ChannelManager() {}~ChannelManager() {}private:vector<Channel> _channels;
};

?2.創建進程池,提供管道條件

// 進程池
class ProcessPool
{
public:ProcessPool(int num) : _process_num(num) {}~ProcessPool() {}bool Create(){int pipefd[2] = {0};for (int i = 0; i < _process_num; i++){// 1.創建管道int n = pipe(pipefd);if (n < 0)return false;}// 2.創建子進程  各自關閉不需要的文件描述符pid_t id = fork();if (id < 0)return false;else if (id == 0){// 子進程 --->讀// 3.關閉不需要的文件描述符close(pipefd[1]);exit(0);}else{// 父進程 --->寫// 3.關閉不需要的文件描述符close(pipefd[0]);}return true;}private:ChannelManager _cm; // 進程鏈int _process_num;   // 進程個數
};

3.父子各自打印驗證是否通信

void BuildChannel(int wfd, pid_t subid){_channels.emplace_back(wfd, subid);// Channel c(wfd,subid);// _channels.push_back(c);}void Print()
{for(auto  &chnnel : _channels){
cout<<chnnel.Name()<<endl;}
}void Work(int rfd){while (true){cout << "我是子進程,我的rfd是:" << rfd << endl;sleep(2);}}
//ProcessPool.hpp
#ifndef __PROCESS__POOL_HPP__
#define __PROCESS__POOL_HPP__
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;
const int gdefaultnum = 5; // 要創建幾個進程
// 先描述 單個進程
class Channel
{
public:Channel(int fd, pid_t id) : _wfd(fd), _subid(id) { _name = "chnnel-" + std::to_string(_wfd) + "-" + std::to_string(_subid); }~Channel() {}int Fd(){return  _wfd;}pid_t Subid(){return  _subid;}string Name(){return  _name;}
private:int _wfd;pid_t _subid;std::string _name;
};// 在組織  進程鏈
class ChannelManager
{
public:ChannelManager() {}~ChannelManager() {}void BuildChannel(int wfd, pid_t subid){_channels.emplace_back(wfd, subid);// Channel c(wfd,subid);// _channels.push_back(c);}void Print()
{for(auto  &chnnel : _channels){
cout<<chnnel.Name()<<endl;}
}
private:vector<Channel> _channels;
};// 進程池
class ProcessPool
{
public:ProcessPool(int num) : _process_num(num) {}~ProcessPool() {}void Work(int rfd){while (true){cout << "我是子進程,我的rfd是:" << rfd << endl;sleep(2);}}bool Create(){for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};// 1.創建管道int n = pipe(pipefd);if (n < 0)return false;// 2.創建子進程  父子各自關閉不需要的文件描述符pid_t id = fork();if (id < 0)return false;else if (id == 0){// 子進程 --->讀// 3.關閉不需要的文件描述符close(pipefd[1]);Work(pipefd[0]);exit(0);}else{// 父進程 --->寫// 3.關閉不需要的文件描述符close(pipefd[0]);_cm.BuildChannel(pipefd[1], id);close(pipefd[1]);}}return true;}void Debug(){_cm.Print();}
private:ChannelManager _cm; // 進程鏈int _process_num;   // 進程個數
};#endif
//Main.cc#include"ProcessPool.hpp"int main()
{ProcessPool pp(gdefaultnum);//創建進程池pp.Create();//打印進程池pp.Debug();sleep(1000);return 0;
}

實現通信?

?4.分配任務,子寫父讀

 void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));if (n > 0){if (n == sizeof(code)){continue;}cout << "子進程[]"<<getpid()<<"]收到一個任務碼:" << code << endl;}else if (n == 0){cout << "子進程退出" << endl;break;}else{// 讀失敗cout << "讀取錯誤" << endl;break;}}}void PushTack(int taskcode){// 1.選擇一個子進程,采用輪詢,防止負載均衡和負載不均衡auto &c = _cm.Select();cout << "選擇一個子進程:" << c.Name() << endl;// 2.發送任務c.Send(taskcode);cout << "發送了一個任務碼:" << taskcode << endl;}

完整代碼

//ProcessPool.hpp
#ifndef __PROCESS__POOL_HPP__
#define __PROCESS__POOL_HPP__
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
using namespace std;
const int gdefaultnum = 5; // 要創建幾個進程
// 先描述 單個進程
class Channel
{
public:Channel(int fd, pid_t id) : _wfd(fd), _subid(id) { _name = "chnnel-" + std::to_string(_wfd) + "-" + std::to_string(_subid); }~Channel() {}int Fd() { return _wfd; }pid_t Subid() { return _subid; }string Name() { return _name; }void Send(int code){int n = write(_wfd, &code, sizeof(code));(void)n;}private:int _wfd;pid_t _subid;std::string _name;
};// 在組織  進程鏈
class ChannelManager
{
public:ChannelManager() : _next(0) {}~ChannelManager() {}void BuildChannel(int wfd, pid_t subid){_channels.emplace_back(wfd, subid);// Channel c(wfd,subid);// _channels.push_back(c);}// 輪詢Channel &Select(){auto &c = _channels[_next];_next++;_next %= _channels.size();return c;}void Print(){for (auto &chnnel : _channels){cout << chnnel.Name() << endl;}}private:vector<Channel> _channels;int _next;
};// 進程池
class ProcessPool
{
public:ProcessPool(int num) : _process_num(num) {}~ProcessPool() {}void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));if (n > 0){if (n == sizeof(code)){continue;}cout << "子進程[]"<<getpid()<<"]收到一個任務碼:" << code << endl;}else if (n == 0){cout << "子進程退出" << endl;break;}else{// 讀失敗cout << "讀取錯誤" << endl;break;}}}bool Create(){for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};// 1.創建管道int n = pipe(pipefd);if (n < 0)return false;// 2.創建子進程  父子各自關閉不需要的文件描述符pid_t id = fork();if (id < 0)return false;else if (id == 0){// 子進程 --->讀// 3.關閉不需要的文件描述符close(pipefd[1]);Work(pipefd[0]);exit(0);}else{// 父進程 --->寫// 3.關閉不需要的文件描述符close(pipefd[0]);_cm.BuildChannel(pipefd[1], id);close(pipefd[1]);}}return true;}void Debug(){_cm.Print();}void PushTack(int taskcode){// 1.選擇一個子進程,采用輪詢,防止負載均衡和負載不均衡auto &c = _cm.Select();cout << "選擇一個子進程:" << c.Name() << endl;// 2.發送任務c.Send(taskcode);cout << "發送了一個任務碼:" << taskcode << endl;}private:ChannelManager _cm; // 進程鏈int _process_num;   // 進程個數
};#endif
//Main.cc#include"ProcessPool.hpp"int main()
{ProcessPool pp(gdefaultnum);//創建進程池pp.Create();//打印進程池//pp.Debug();int task_code = 1;while(true){pp.PushTack(task_code++);sleep(1);}return 0;
}

創建進程池后,OS關閉了沒有意義的管道,每次選擇一個管道接受消息?

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

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

相關文章

vue computed 計算屬性簡述

Vue 的 ?計算屬性&#xff08;Computed Properties&#xff09;? 是 Vue 實例中一種特殊的屬性&#xff0c;用于?聲明式地定義依賴其他數據動態計算得出的值?。它的核心優勢在于能夠自動追蹤依賴關系&#xff0c;并緩存計算結果&#xff0c;避免重復計算&#xff0c;提升性…

CSS塊元素、行內元素、行內塊元素詳解

一、塊元素&#xff08;Block Elements&#xff09; 1.定義與特點 獨占一行&#xff1a;默認情況下&#xff0c;塊元素會從新的一行開始&#xff0c;并且其后的元素也會被推到下一行。可設置寬高&#xff1a;可以自由設置寬度&#xff08;width&#xff09;和高度&#xff08…

Vue3項目中可以嘗試封裝那些組件

在 Vue 3 項目中&#xff0c;組件的封裝可以根據功能、復用性和業務需求進行劃分。以下是一些常見的組件類型&#xff0c;適合封裝為獨立組件&#xff1a; 1. 基礎 UI 組件 按鈕 (Button) 封裝不同樣式、大小、狀態的按鈕。支持 disabled、loading 等狀態。 輸入框 (Input) 封…

2025年AI搜索引擎開源項目全景指南:從核心框架到生態工具

2025年AI搜索引擎開源項目全景指南&#xff1a;從核心框架到生態工具 在人工智能技術迅猛發展的當下&#xff0c;開源項目已成為構建AI搜索引擎的核心驅動力。本文整理9個具有代表性的開源項目&#xff0c;涵蓋搜索框架、擴展生態及底層支持技術&#xff0c;助你快速搭建或優化…

Word 小黑第22套

對應大貓23 續編號&#xff08;編號斷了&#xff0c;從一開始&#xff09;&#xff1a;點編號&#xff0c;再設置編號值 插入以圖標方式顯示的文檔&#xff1a;插入 -對象 -由文件創建 &#xff08;這里要鏈接到文件也要勾選 不然扣一分&#xff09; 一個頁面設為橫向不影響上…

平面波揚聲器 VS球面波揚聲器的原理與優缺點對比

一、核心定義與原理 1、平面波揚聲器 1.1、平面波揚聲器的定義?&#xff1a;通過“相控陣”技術控制聲波相位&#xff0c;使聲波以平行線&#xff08;面&#xff09;定向傳播的揚聲器&#xff0c;聲波近似平面振動&#xff0c;能量集中且衰減緩慢?。 1.2、平面波揚聲器的原…

設計模式之命令設計模式

命令設計模式&#xff08;Command Pattern&#xff09; 請求以命令的形式包裹在對象中&#xff0c;并傳給調用對象。調用對象尋找可以處理該命令的對象&#xff0c;并把該命令傳給相應的對象執行命令&#xff0c;屬于行為型模式命令模式是一種特殊的策略模式&#xff0c;體現的…

EcoVadis新增可持續發展徽章

EcoVadis新增的兩項新徽章旨在進一步激勵和表彰企業在可持續發展方面的努力和成就。以下是這兩項新徽章的概述&#xff1a; 可持續發展之旅徽章&#xff08;Sustainability Journey Badge&#xff09;&#xff1a; 目的&#xff1a;表彰那些在可持續發展方面展現出持續進步和承…

力扣hot100二刷——二叉樹

第二次刷題不在idea寫代碼&#xff0c;而是直接在leetcode網站上寫&#xff0c;“逼”自己掌握常用的函數。 標志掌握程度解釋辦法?Fully 完全掌握看到題目就有思路&#xff0c;編程也很流利??Basically 基本掌握需要稍作思考&#xff0c;或者看到提示方法后能解答???Sl…

從“自習室令牌”到線程同步:探秘鎖與條件變量

目錄 互斥 為什么需要鎖 鎖的原理--互斥 鎖的使用 同步 鎖的問題 條件變量 互斥 為什么需要鎖 先看結果&#xff1a; 以下代碼是我模擬創建線程搶票&#xff0c;由于不加鎖導致票搶到了負數 main.cc: #include<vector> #include<iostream> #include"…

字符串哈希從入門到精通

一、基本概念 字符串哈希是將任意長度的字符串映射為固定長度的哈希值&#xff08;通常為整數&#xff09;的技術&#xff0c;核心目標是實現O(1)時間的子串快速比較和高效查詢。其本質是通過數學運算將字符串轉換為唯一性較高的數值&#xff0c;例如&#xff1a; ??????…

什么是數學建模?數學建模是將實際問題轉化為數學問題

數學建模是將實際問題轉化為數學問題&#xff0c;并通過數學工具進行分析、求解和驗證的過程。 一、數學建模的基本流程 問題分析 ? 明確目標&#xff1a;確定需要解決的核心問題。 ? 簡化現實&#xff1a;識別關鍵變量、忽略次要因素。 ? 定義輸入和輸出&#xff1a;明確模…

搭建主從服務器

任務需求 客戶端通過訪問 www.nihao.com 后&#xff0c;能夠通過 dns 域名解析&#xff0c;訪問到 nginx 服務中由 nfs 共享的首頁文件&#xff0c;內容為&#xff1a;Very good, you have successfully set up the system. 各個主機能夠實現時間同步&#xff0c;并且都開啟防…

【python web】一文掌握 Flask 的基礎用法

文章目錄 一、 Flask 介紹1.1 安裝 Flask二、Flask的基本使用2.1 創建第一個 Flask 應用2.2 路由與視圖函數2.3 請求與響應2.4 響應對象2.5 模板渲染2.6 模板繼承2.7 靜態文件管理2.8 Blueprint 藍圖2.9 錯誤處理三、Flask擴展與插件四、部署 Flask 應用五、總結Flask 是一個輕…

最長最短單詞(信息學奧賽一本通-1143)

【題目描述】 輸入1行句子(不多于200個單詞&#xff0c;每個單詞長度不超過100)&#xff0c;只包含字母、空格和逗號。單詞由至少一個連續的字母構成&#xff0c;空格和逗號都是單詞間的間隔。 試輸出第1個最長的單詞和第1個最短單詞。 【輸入】 一行句子。 【輸出】 第1行&…

AlexNet 有哪些首創?

現在大家每逢討論人工智能&#xff0c;都離不開深度學習&#xff0c;這輪深度學習的熱潮&#xff0c;追根溯源可以到2012年 AlexNet 的橫空出世。后來&#xff0c;大家開始發現深度學習越來越強的能力。 AlexNet 的首創貢獻 AlexNet&#xff08;2012年&#xff09;作為現代深…

【Linux我做主】基礎命令完全指南上篇

Linux基礎命令完全指南【上篇】 Linux基礎命令完全指南github地址前言命令行操作的引入Linux文件系統樹形結構的根文件系統絕對路徑和相對路徑適用場景Linux目錄下的隱藏文件 基本指令目錄和文件相關1. ls2. cd和pwdcdpwd 3. touch4. mkdir5. cp6. mv移動目錄時覆蓋寫入的兩種特…

OceanBase 用戶問題精選答疑:OceanBase 版本升級解析

背景 此篇博客的源自于OceanBase社區論壇內一位名為皇甫侯的熱心用戶所提的建議&#xff0c;希望向OceanBase的用戶介紹OceanBase的版本升級路徑。本文以一個版本升級為示例&#xff0c;匯總了對用戶而言比較重要的版本升級要點&#xff0c;期望通過這份分享&#xff0c;能讓讀…

Docker Desktop 安裝與使用詳解

目錄 1. 前言2. Docker Desktop 安裝2.1 下載及安裝2.2 登錄 Docker 賬號2.3 進入 Docker Desktop 主界面 3. Docker 版本查看與環境檢查3.1 查看 Docker Desktop 支持的 Docker 和 Kubernetes 版本3.2 檢查 Docker 版本 4. Docker Hub 和常用鏡像管理方式4.1 使用 Docker Hub4…

英文LaTeX中左右引號怎么打

在英文 LaTeX 中&#xff0c;要輸入左右引號&#xff0c;可以使用以下命令&#xff1a; 左雙引號&#xff1a;&#xff08;兩個反引號&#xff09;右雙引號&#xff1a;&#xff08;兩個單引號&#xff09; 例如&#xff1a; This is a quoted text.這將顯示為&#xff1a; …