【Linux系统编程十九】:(进程通信)--匿名管道/模拟实现进程池

【Linux系统编程十九】:匿名管道原理/模拟实现进程池

  • 一.进程通信理解
  • 二.通信实现原理
  • 三.系统接口
  • 四.五大特性与四种情况
  • 五.应用场景--进程池

一.进程通信理解

什么是通信?

通信其实就是一个进程想把数据给另一个进程,但因为进程具有独立性,想直接给是不行的。
所以就必须得要有通信的方案。

为什么要通信?
主要是要么是传数据,要么传指令,要么是我们进行多进程间的协同,要么是可能是一个进行想通知另一个地方某些事情发生了。
啊,不管什么原因,反正就是要通信。

怎么通信?
但通信的时候呢,那么对我们来讲呢,竞争具有独立性,那通信就有成本,那怎么办呢?
所以通信它的实现方案,那么这里呢本质就一定是先要要让不同的进程先看到同一份公共的资源。
这份公共资源不能属于我们通信的进程当中的任何一个。
啊,因为竞争具有独立性,如果属于任何一个的话,那这个资源就不应该让其他竞争看到。
所以这个资源呢就必须只能由操作系统提供。
一般而言必须只能由操作系统提供。
而我们对应的呃就是操作系统提供的资源呢,是不允许任何进程直接去访问的。
啊,所以就注定了我们一定要提供大量的我们关于就业通信的相关的系统调用接口。
所以从底层设计上呢,我们的操作系统既要给我们提供通信的方案,又要给我们提供那么进行通信让用户所调用的接口。
如果我们一个文件,它能够被多个进程打开并访问。
那么我们文件呢,这玩意儿就不就是一个公共资源了吗?
等一个进程往文件里写,另一个进程从文件里读。
只要你写完之后把数据直接刷新,刷新到磁盘上,另一个我们对应的这个进程再从文件里读,不就读到数据了吗?
好,同学们,那么这种呢那么思想呢其实对的吗?
那么所谓的管道呢,其实就是基于文件的一种通信方式。
他的思想呢其实跟我刚刚说的那个其实大差不差,只不过呢他并不把数据往磁盘当中做刷新。
谁规定数据一定要刷到磁盘上?
那么管道通信时,只要建立好双方的通信信道,往里写,你去读就可以。
不一定非得用我们对应的文磁盘呀,我们用内存也可以。
在这里插入图片描述

二.通信实现原理

我们每一个文件呢,它都天然的要提供一个属于自己对应的,叫做页缓冲区。全称叫做文件页缓区,我们叫做缓冲区。

从理论上来讲呢,如果这个文件就是一个普通文件,我们这个文件一定是曾经要么在在磁盘当中被新建的,要么在我们磁盘当中被打开的。
反正我们最终那么一个文件在磁盘当中,假设它也存在的话,它会在特定的分区,特定的分组,有自己的属性和数据块。在操作系统内核当中,它会存在非常非常多的内存级文件。
也就是这样的文件呢,我们并不需要在我们对应的磁盘当中真正的存在。
啊,那么而是呢最终只要能够在内存里让我们那么能够把它用起来就可以。我们只需要把我们曾经学到的知识里面不要做刷新,那么剩下的这不就是内存级文件吗

在这里插入图片描述
让进程打开一个文件时,再创建一个子进程。子进程会进程父进程的PCB,页表,地址空间。但文件不会继承。
这样父进程里的文件描述符表和子进程的文件描述符表都指向一样的文件。
刚刚新建的这个内存及文件。父子进程是不是都可以访问,都可以被父子进程看到。
还记得进程间通信的本质吗:让不同的进程看到同一份资源。这不就做到了吗。

进程先打开文件,在fok之后创建子进程进程。 此时如果我们有能力打开这种文件的话,那么此时它不就叫做父进程程和子进程看到了同一个文件吗?那么这个同一个文件它是内存级的。因为每个文件都存在自己对应的缓冲区。
双方就可以实现进程间通信了。

在系统当中父进程在打开我们对应的一个文件的时候呢。
它并不是只是单方面的去把一个文件以读写方式打开,或读或写都不是。他在创建这个管道时,把同一个文件既以读方式打开,又以写方式打开。
所以父进程打开文件的时候,以读写方式整体把管道文件打开。
那么接下来呢我们的父进程呢在fork创建出子进程后。
子进程它会拷贝父进程的文件描述符表,所以它们两个当中父进程和子进程都会有对应的读写端。指向同一个缓冲区
在这里插入图片描述

好,这就是我们的管道啊。
至于父进程是读,子进程写,还是父进程是写,子进程是读,需要用户来决定?
只要让父进程和子进程它们各自要关闭对应的读写端,来形成一个叫做单向通信的信道。比如说图当中呢它是想要父进程进行写入。子进程读取,只要将父进程就把曾经自己的读端这个关掉。对应的子进程呢,它想对我们对应的管道来进行读取读取,所以他就把自己的写入关掉。
关掉了这次我们的父子进程就可以使用,父进程剩下一个写端,子进程剩下一个读端,那么建立了这样的一种通信信道。
在这里插入图片描述
这种基于文件级别的通信方式,那么正是因为它只能进行单向通信。所以我们命名为叫做管道。

以上所做的工作叫做建立通信信道。现在父子进程还没进行通信。

三.系统接口

在这里插入图片描述
这个系统调用接口很简单,它的参数是一个数组,数组只有两个参数。而这个参数是作为输出型参数的。将文件的读写描述符带出来。默认pipefd[0]是读端,pipefd[1]是写端。
而这个系统调用所做的工作就是我们上面所讲的实现原理做的工作即建立信道。这个信道是由固定大小的,一般为64KB.
创建完信道后,我们还有件事情,就是要形成单向信道,这件事需要让父子进程共同完成,父进程需要读,就要手动将写端关闭。
子进程需要写,就要手动将读端关闭。
最基本的一个叫做我们建立单向信道的过程。建立完单向信道后,就可以往信道里写入信息了。写入信息时要注意管道本质上是文件。而文件本质上是内核资源。操作系统允不允许你的父进程和子进程直接去访问这个文件资源呢?只能通过系统调用接口去访问,所以只能使用write和read系统调用接口进行。

四.五大特性与四种情况

在这里插入图片描述
管道的特性有五个:
一:只有具有血缘关系的进程才可以进行通信。在这里插入图片描述

二:管道只能单向通信。
三:父子进程是会进行协同的。
四:管道是面向字节流的。
五:管道本质就是文件,属于内核资源,进程结束就会自动释放。

管道还会出现四种情况:
1.(写段写的速度比读端要慢)读写端都正常,管道如果为空,那么读端就要阻塞。
2.(写端写的速度比读端要快)读写端正常,管道如果被写满,写段就要阻塞。
3.(写端关闭)读端正常,写端关闭,读端就会读到0,表面读到了管道的结尾处,读端对应的进程并不会阻塞。
4.(读端关闭)写端正常,读端关闭,操作系统就要杀死正在写入的进程,因为没有意义。利用信号杀死。

五.应用场景–进程池

在shell里就存在管道,竖画线它就表示管道。
第一,当我用管道集连起对应的这若干个命令时。
那么其中每一个对应的命令最终都会被直接启动成一个进程。
也就是他们这些进程是同时被起来的啊,也就就是你在跑的同时我也在跑,只不过我在等你啊。这表面它们之间都是子进程。谁的子进程呢?bash的子进程,它们都是bash的子进程,所以可以利用管道。
两个竖画线分割三个命令。创建两个管道之后,然后连续再创建三个子进程,然后每个进程程序计划执行不同的命令。在执行之前呢,我们需要对每一个进程的标准输出啊标准输入进行一下重定向。每一个管道它都有自己的读端和写端。勾连起这几个命令,然后呢让他们直接互相通信起来。
第一个进程的标准输出重定向到我们管道的写端,中间进程的标准输入重定向到上一个管道的读端。表示我本来往显示器上进行打印,现在呢把显示器上打印改成向管道里去进行我们对应的写入。
中间有个进程呢,它呢本来读的时候是从我们对应的键盘读取,今天读的时候,我就不要让它从键盘读,而是从我们对应的管道文件里读,显示的时候就不用再写了,不用向显示器写了,而是继续向后面的管道再继续写入啊,所以做一堆的重定向。
最后一个进程的话,做一下输入重定向。这就是shell中的重定向实现原理。

管道还有一个应用场景:进程池。

我们能不能提前把一批任务先建立好,当有任务到来时,然后我直接指派给其中一个进程呢?。
其中我们一次把对应的一批进程直接创建好,那么这个工作我们就叫做我们先进行一次,叫做进程池的储备。
也就是把一个一个的进程呢当做一一份一份儿一份的资源,提前储备好,提前做个好。 当我们需要的时候再去指派,让他去帮我们去完成任务。

在这里插入图片描述

首先对应的父进程。在正式接受新需求,接受新任务之前。
啊,我先一次性同时创建出若干个子进程。然后呢为了后面呢更好的去控制上面的所有的子进程。
我呢想做这样的工作,为我的父进程想和其中我所创建的每一个子进程。都建立一条叫做管道的信道。
一个进程建立一个管道和第二个进程建立一个管道和第三个建立管道,第四个建立管道和第五个建立管道和第六个建立管道……,以此类推。
然后我们让每一个子进程只从管道当中进行读取。父进程呢它把控这批管道。那么父进程想那往哪个管道里写内容,他就可以直接向哪个管道里写内容。
那么其中我们对应的父进程,如果没有像第一个管道里写任何内容,请问这个子进程在干什么?
这个子进程它在读取等待我们管道流数据。
那么也就是说子进程当前就阻塞在这个管道当中,他就在等这个父进程给他任务呢。
那么父进程呢,不给你写,子进程就等着呗。
然后父进程一旦向管道当中写了,写了之后,那么这个进程会读到对应的数据,然后这个子进程会继续向后执行。向后我就提前可以让这个进场。那么结合他读进来的数据以及他向后执行这个动作,然后我们就可以让这个子进程上去执行对应的任务了。
好,换句话说,从此往后我的父进程,我们规定父进程向子进程当中管道里写的,我们把它都叫做一个一个的任务。(父子通信时,那么父进程每次写入时只能有写四个字节)
我们对应的父进程呢,他想布置任务的时候,他无非就是做两件事情。第一个叫做选择任务。第二个叫做选择进程。
好,也就是他把任务确定好了。第二他把进程确定好了,他就可以把这个任务指派给其中的某一个子进程去运行。


#include "TASK.hpp"
#include <iostream>
#include <string>
#include <unistd.h>
#include <vector>
#include <sys/wait.h>
#include <sys/stat.h>
#include <time.h>
#define N 5std::vector<task_t> tasks;
//master----[]---slaver,master通过信道控制子进程
//先描述,再组织:这个信道是由管道和子进程的pid构成class channel
{
public:channel(int cmdfd,pid_t slaverid,const std::string &processname):_cmdfd(cmdfd),_slaverid(slaverid),_processname(processname){}public:int _cmdfd;//发送任务的文件描述符pid_t _slaverid;//子进程的pidstd::string _processname;//子进程的名字
};
std::vector<channel> channels;
//在创建子进程之前创建管道,然后让父子进程分别关闭一端
void slaver()
{//子进程直接从标准输入里就可以获取到任务码,就没有管道的概念了while(1){int cmdcode=0;//将任务码带出来int n=read(0,&cmdcode,sizeof(int));if(n==sizeof(int)){//根据任务码的不同执行不同的函数std::cout<<"slaver say@ get a command:"<<getpid()<<": code--> "<<cmdcode<<std::endl;    tasks[cmdcode]();}if(n==0)//说明读取完 ,没有可读的了,子进程就可以退出了break;}sleep(3);
}
void InitProcesspoll(std::vector<channel>* channels)//输出型参数用指针
{for(int i=0;i<N;i++){int pipefd[2];pipe(pipefd);//创建管道pid_t id=fork();if(id==0){close(pipefd[1]);//子进程,读,关闭写dup2(pipefd[0],0);//--重定向到标准输入slaver();//子进程执行任务,从信道里读取任务码,封装成一个函数,但这里我们可以重定向,向标准输入里读取exit(0);}close(pipefd[0]);//父进程写,关闭读//管道和子进程都创建好了,可以初始化信道了std::string name="process-"+std::to_string(i);channels->push_back(channel(pipefd[1],id,name));     }
}
void ctrlSlaver(std::vector<channel>& channels)//输入输出型参数用引用
{//接下来就是父进程控制子进程发送任务给子进程int cnt=5;while(cnt--){//1.选择任务int cmdcode=rand()%tasks.size();//2.选择进程int processpos=rand()%channels.size();//可以通过这个位置找到信道,找到子进程std::cout<<"father say comcode:"<<cmdcode<<" have send to"<<channels[processpos]._slaverid<<"->"<<channels[processpos]._processname<<std::endl;//3.发送任务//选好子进程后,就可以往这个信道里发送任务write(channels[processpos]._cmdfd,&cmdcode,sizeof(int));sleep(2);}
}
void QuitProcess(const std::vector<channel>& channels)
{//如何关闭子进程?只要让写端关闭,读端读到0就自然会跳出循环for(auto& e: channels){close(e._cmdfd);}sleep(5);//子进程跳出循环后就会退出,父进程等待子进程即可for(auto& e:channels){waitpid(e._slaverid,nullptr,0);}
}
int main()
{srand(time(nullptr));//生成随机数LoadTask(&tasks);//1.把任务加载进来InitProcesspoll(&channels);//2.初始化-->创建管道,创建子进程,初始化信道。ctrlSlaver(channels); //3.开始控制子进程QuitProcess(channels);//4.清理收尾}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/190739.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十一周任务总结

本周任务总结 本周物联网方面主要继续进行网关的二次开发与规则引擎实现设备联动的实现 非物联网方面主要复习了docker的使用与算法的学习 1.网关的二次开发&#xff0c;本周将实现debug调试输出的文件下载到了网关&#xff0c;但网关出了问题无法连接&#xff0c;最终跟客服…

简单算法——回溯、贪心、动态规划

回溯法 回溯法深度优先剪枝&#xff0c;实质就是用递归代替for循环。 仍然是一种暴力遍历的手段&#xff0c;通常与递归配合使用&#xff0c;用于解决单纯for循环无法处理的问题&#xff0c;比如组合、切割、子集、排列等问题——比如求n个数里的长度为k的组合&#xff0c;需要…

《循环双向链表》(带哨兵位的头节点)

目录 ​编辑 前言&#xff1a; 关于双向循环带头链表: 模拟实现双向循环带头链表&#xff1a; 1.typedef数据类型 2.打印链表 3.初始化链表&#xff1a; 4.创建节点 5.尾插 6.头插 7.尾删 8.头删 9.寻找节点 10.在节点前插入 11.删除指定节点 单链表和双链表的区别…

c语言免杀火绒

文章目录 前记c加载器补充知识 前记 pyinstaller pyinstaller目前已经被杀疯了&#xff0c;简单打包一个hello a"hello" print(a)#pyinstaller -F -w b.py -n HipsMain.exe考虑Nuitka pip uninstall nuitka pip install nuitka pip install nuitka1.8.5 这里最新…

Python使用大连理工情感本体提取文本的情感倾向

import pandas as pd # 导入词典 df pd.read_excel(Sentiment_dictionary\大连理工情感词汇本体\情感词汇本体.xlsx) # 我们暂时只使用 [词语,词性种类,词义数,词义序号,情感分类,强度,极性] df df[[词语, 词性种类, 词义数, 词义序号, 情感分类, 强度, 极性]] df.head()# 按…

scss的高级用法——循环

周末愉快呀&#xff01;一起来学一点简单但非常有用的css小知识。 最近在一个项目中看到以下css class写法&#xff1a; 了解过tailwind css或者unocss的都知道&#xff0c;从命名就可以看出有以下样式&#xff1a; font-size: 30pxmargin-left: 5px;margin-top: 10px; 于是…

【字符编码系列一】ASCII编码是什么?

介绍 ASCII 编码于 1967 年第一次发布&#xff0c;最后一次更新是在 1986 年&#xff0c;迄今为止共收录了 128 个字符&#xff0c;包含了基本的拉丁字母&#xff08;英文字母&#xff09;、阿拉伯数字&#xff08;也就是 1234567890&#xff09;、标点符号&#xff08;,.!等&…

Java Fasn 带您谈谈——开源、闭源

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

【全网首发】2023年NOIP真题

目录 前言 真题 结尾 前言 NOIP题目了解一下&#xff0c;后续有可能会出讲解&#xff0c;题目全部来自于洛谷 真题 第一题&#xff1a;词典 第二题&#xff1a;三值逻辑 第三题&#xff1a;双序列扩展 第四题&#xff1a; 天天爱打卡 结尾 大家可以把你的预期分数打在评论…

JS判断是否存在某个元素(includes、indexOf、find、findeIndex、some)(every 数组内所有值是否相同)

方法一&#xff1a;array.includes(searcElement[,fromIndex]) 此方法判断数组中是否存在某个值&#xff0c;如果存在返回true&#xff0c;否则返回false。 searchElement&#xff1a;需要查找的元素&#xff0c;必选。fromIndex&#xff1a;可选&#xff0c;从该索引处开始查…

Android 解决CameraView叠加2个以上滤镜拍照黑屏的BUG (二)

1. 前言 这段时间&#xff0c;在使用 natario1/CameraView 来实现带滤镜的预览、拍照、录像功能。 由于CameraView封装的比较到位&#xff0c;在项目前期&#xff0c;的确为我们节省了不少时间。 但随着项目持续深入&#xff0c;对于CameraView的使用进入深水区&#xff0c;逐…

【项目管理】PMO技能树21项参照

导读&#xff1a;PMO技能树让你能够有全局视野&#xff0c;让你对照着检查自己的能力是否掌握。技能树提供了构建个人知识体系参照和地图导航&#xff0c;不至于迷失方向。 目录 1、PMO层次概览 2、技能树 2.1 项目管理流程 2.2 项目组合管理 2.3 风险管理 2.4 项目资源管…