0


【Linux】匿名管道与命名管道,进程池的简易实现

文章目录


前言

一、匿名管道

#include<unistd.h>
功能:创建一无名管道
原型
intpipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

1.管道原理

在这里插入图片描述
在这里插入图片描述

本质是先让不同的进程看到同一份资源,也就是两个进程都能对管道文件的缓冲区进行操作

这里我们pipe的时候,会使用两个文件描述符,这两个文件描述里面存的file结构体是同一个,也就是管道文件的file结构体,file结构体中存储有inode以及系统缓冲区,此时fork一个子进程,子进程有着和父进程一样的结构,
这里有一个非常重要的点虽然子进程有着自己的进程地址空间,也有着自己存储file结构体的指针数组,但是其数组里面的内容是和父进程一样的,也就是子进程里面pipe对应的文件描述符位置指向的file结构体(管道文件)是同一个,至此我们父子进程就看到了同一个资源,可以利用这个资源进行通信

两个不同的进程打开同一份文件的时候,在内核中,操作系统只会打开一个
在这里插入图片描述

2.管道的四种情况

1.读写端正常,管道如果为空,读端就要阻塞
读写端正常,管道如果被写满,写端就要阻塞
2.读端正常读,写端关闭,读端就会读到0,表明读到了管道文件的结尾,不会被阻塞,如果我们打印读端读到的内容,显示器会一直显示0
3.写端正常写入,读端关闭,操作系统会杀掉此时正在写入的进程(通过信号来杀掉)
4.因为操作系统不会做低效,浪费的事情,我读端都不读了,你写入再多数据到一个管道里面有什么用,因为管道不占用磁盘内存,所以程序结束后,就没有管道的存在了。

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

3.管道的特点

1.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
2.管道提供流式服务
3.一般而言,进程退出,管道释放,所以管道的生命周期随进程
4.一般而言,内核会对管道操作进行同步与互斥
5.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

二、命名管道

1. 特点

1.管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
2.如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
3.命名管道是一种特殊类型的文件

2.创建命名管道

1.在命令行上

mkfifo  +文件名

2.在程序中

intmkfifo(constchar*filename,mode_t mode);intmain(int argc,char*argv[]){mkfifo("p2",0644);return0;}

3.一个程序执行打开管道并不会真正打卡

在这里插入图片描述
在这里插入图片描述

我们执行这个程序发现并没有打印那句话,说明管道文件并没有真正打开,只有当我们执行另一个我们要通信的文件的时候,管道才会真正打开
在这里插入图片描述

三、进程池简易实现

1.makefile

ProcessPool:ProcessPool.cpp
    g++-o $@ $^-std=c++11-g

.PHONY:clean
clean:
    rm -rf ProcessPool

2.Task.hpp

#pragmaonce#include<functional>#include<vector>#include<iostream>usingnamespace std;voidtask1(){
    std::cout <<"lol 刷新日志"<< std::endl;}voidtask2(){
    std::cout <<"lol 更新野区,刷新出来野怪"<< std::endl;}voidtask3(){
    std::cout <<"lol 检测软件是否更新,如果需要,就提示用户"<< std::endl;}voidtask4(){
    std::cout <<"lol 用户释放技能,更新用的血量和蓝量"<< std::endl;}voidLoadTask(vector<function<void()>>*tasks){
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task3);
tasks->push_back(task4);return;}

3.ProcessPool.cpp

在这里插入图片描述

我们创建processnum个子进程,让父进程来写,子进程来读,子进程读到任务号后进行对应的处理。

#include<iostream>#include"Task.hpp"#include<assert.h>#include<vector>#include<string>#include<unistd.h>#include<sys/wait.h>#include<sys/types.h>usingnamespace std;
vector<function<void()>> tasks;constint processnum =10;//创建的子进程数classchannel{public:channel(  string processname,  pid_t slaverid,int cmdcode):_processname(processname),_cmdfd(cmdcode),_slaverid(slaverid){}public:
    string _processname;//执行任务的进程名

    pid_t _slaverid;//执行任务的进程pidint _cmdfd;//朝几号管道去操作};voidMenu(){
    std::cout <<"################################################"<< std::endl;
    std::cout <<"# 1. 刷新日志             2. 刷新出来野怪        #"<< std::endl;
    std::cout <<"# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #"<< std::endl;
    std::cout <<"#                         0. 退出               #"<< std::endl;
    std::cout <<"#################################################"<< std::endl;}voidslaver(){int cmdcode;while(true){int n =read(0,&cmdcode,sizeof(int));//读取任务码if(n ==sizeof(int)){
            cout <<"slaver say get a command "<<getpid()<<" cmdcode:  "<< cmdcode << endl;if(cmdcode >=0&& cmdcode < tasks.size())
                tasks[cmdcode]();//执行任务}elseif(n ==0)//为0,说明读到文件末尾,之间breakbreak;}}voidInitProcessPool(vector<channel>*channels){for(int i =0; i < processnum; i++){int pipefd[2]={0};int n =pipe(pipefd);//使用两个文件描述符指向同一个管道文件assert(!n);
        pid_t id =fork();if(id ==0)//子进程{close(pipefd[1]);//关闭写文件dup2(pipefd[0],0);//将读文件重定向到标准输入的位置close(pipefd[0]);//关闭当前读文件,因为我们后续用标准输入的下标就行了slaver();//子进程读取任务码exit(0);}
        string name ="processname "+to_string(i);//子进程名字
        channels->push_back(channel(name, id, pipefd[1]));//子进程pid,这个子进程//与父进程之间的管道文件描述符下标记录下来// fatherclose(pipefd[0]);//关闭读文件}}voidctrlProcess(vector<channel>&channels){int which =0;//我们循环调用各个子进程,which为子进程的下标while(true){Menu();int select =0;
        cin >> select;
        
        cout <<"Please Enter@ ";if(select <=0|| select >=5)break;int cmdcode = select -1;

        cout <<"father say task have sent to "<< channels[which]._processname <<"  cmdcode : "<< cmdcode << endl;write(channels[which]._cmdfd,&cmdcode,sizeof(int));//写入指令
        which++;
        which %= channels.size();}}voidQuitProcess(const vector<channel> channels){//方法一:for(constauto&c : channels)close(c._cmdfd);for(constauto&c : channels)waitpid(c._slaverid,nullptr,0);//方法二://for(int i=channels.size()-1;i>=0;i--){//  close(channels[i]._cmdfd);//waitpid(channels[i]._slaverid,nullptr,0);//阻塞等待//}}intmain(){

    vector<channel> channels;//管理管道的数组LoadTask(&tasks);//加载任务InitProcessPool(&channels);//初始化进程池ctrlProcess(channels);//输入任务命令QuitProcess(channels);//中止进程return0;}

如果等待和close在一个循环中会发生阻塞,因为我一号管道虽然父进程那里写关闭了,但依旧有子进程2,3指向这个管道为写

在这里插入图片描述

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/m0_74774759/article/details/134605485
版权归原作者 Kaugo 所有, 如有侵权,请联系我们删除。

“【Linux】匿名管道与命名管道,进程池的简易实现”的评论:

还没有评论