【Linux】第三十一站:管道的一些应用

文章目录

  • 一、我们之前的|(竖划线)管道
  • 二、自定义shell
  • 三、使用管道实现一个简易的进程池
    • 1.详解
    • 2.代码
    • 3.一个小bug
    • 4.最终代码

一、我们之前的|(竖划线)管道

cat test.txt | head -10 | tail -5

如上代码所示,是我们之前所用的管道

我们拿下面这个举个例子

image-20240118174346949

当我们用管道级联起这些命令的时候,每一个命令最终都会被变为一个进程。这些进程的父进程都是同一个,我们可以去看一下这个父进程是什么东西

image-20240118174621963

我们可以注意到,这个其实就是bash

所以这批进程具有血缘关系

所以他们就可以进行通信,从而达到目的

二、自定义shell

如果我们想让我们的shell支持这个|管道,代码该如何写呢?

我们可以在以前的shell代码中进行适量的修改,添加这些模块的功能即可

image-20240118180936858

​ 4.0 分析输入的命令行字符串,获取有多少个|,命令打散多个子命令字符串

​ 4.1 malloc申请空间,pipe先申请多个管道

​ 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始,输出重定向,指定的第一个程序的1号文件要变成第一个管道文件的写端

​ 4.3 中间的需要进行输入输出重定向,0标准输入替换为上一个管道的读端,1标准输入替换为下一个管道的写端

​ 4.4 最后一个要进行输入重定向, 将0标准输入替换为上一个管道的读端

​ 4.5 分别让不同的子进程执行不同的命令 — exec* —exec* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向

下面是曾经的shell的代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; const char* getusername()
{return getenv("USER");
}
const char* Gethostname()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{char* pos = cmd;while(*pos!='\0'){if(*pos == '>'){if(*(pos + 1) == '>'){*pos++ = '\0';*pos++ = '\0';while(isspace(*pos)) pos++;rdirfilename = pos;rdir=APPEND_RDIR;break;}else {*pos = '\0';pos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir = OUT_RDIR;break;}}else if(*pos == '<'){*pos = '\0';  //ls -a -l < file.txtpos++;while(isspace(*pos)) pos++;rdirfilename = pos;rdir = IN_RDIR;break;}else {// do nothing}pos++;}
}
void Interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{if(strcmp(cline,"") == 0) return 0;int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){int fd = 0;if(rdir == IN_RDIR){fd = open(rdirfilename,O_RDONLY);dup2(fd,0);}else if(rdir == OUT_RDIR){fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}else if(rdir == APPEND_RDIR) {fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fd,1);}//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}int BuildCommand(char* _argv[],int _argc)
{if(_argc == 2 && strcmp(_argv[0],"cd") == 0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0],"export") == 0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0],"echo") == 0){if(strcmp(_argv[1],"$?") == 0){printf("%d\n",last_code);last_code = 0;}else if(*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);if(val) printf("%s\n",val);}else {printf("%s\n",_argv[1]);}return 1;}if(_argc > 0 && strcmp(_argv[0],"ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}
int main()
{while(!quit){rdirfilename = NULL;rdir = NONE;//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;//  for(int i = 0; argv[i]; i++)//  {//      printf("[%d] : %s\n",i,argv[i]);//  }//4.指令的判断(内建命令和普通命令)int n = BuildCommand(argv,argc);// 4.0 分析输入的命令行字符串,获取有多少个|,命令打散多个子命令字符串// 4.1 malloc申请空间,pipe先申请多个管道// 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始,输出重定向,指定的第一个程序的1号文件要变成第一个管道文件的写端// 4.3 中间的需要进行输入输出重定向,0标准输入替换为上一个管道的读端,1标准输入替换为下一个管道的写端// 4.4 最后一个要进行输入重定向, 将0标准输入替换为上一个管道的读端// 4.5 分别让不同的子进程执行不同的命令 --- exec*  ---exec* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向//5.普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

三、使用管道实现一个简易的进程池

1.详解

池化技术就是因为频繁的系统调用成本也挺高的,不妨直接创建一批进程,然后在进行分配。

我们首先让父进程去创建一批进程

image-20240118182313941

然后,我们让父进程和子进程之间创建管道,子进程是读端,父进程是写端

image-20240118182825904

如果父进程没有往子进程里面写任何内容,那么子进程在阻塞等待,想要读取管道里面的内容

一旦写了,那就可以执行了。

我们将父进程往子进程里面写的,可以叫做一个一个的任务。我们这里简单的规定用四字节的任务码来进行写入和读取。这个数据的不同值代表着不同的任务。所以我们可以将这个父进程称作master,子进程可以称作slaver或者worker

我们这里的流程就是

  1. 选择任务
  2. 选择进程

然后就是直接执行了

如下代码是我们第一步先写出来的

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
const int processnum = 5;
//先描述
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;//子进程的名字
};void slaver(int rfd)
{while(1){std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;sleep(100000);}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  close(pipefd[1]);slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}int main()
{//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程//3.清理收尾sleep(1000);return 0;
}

在上面的代码中,我们完成了初始化过程。

即先创建了足够多的子进程,然后分别进行初始化。不过在上面的过程中,有一个需要注意的事项是这样的,所有子进程的读端都是3号文件,这是因为,第一次我们父进程将3,4给创建好管道以后,然后父进程又将3号管道给关闭了,又接着创建了3,5的管道。如此一来,所有的子进程他的读取端都是3号文件了。这样做也没有问题。

不过我们还有一种更为优雅的做法

image-20240119002724340

image-20240119002749177

接下来我们完成第二步,让父进程产生任务分配给子进程

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
const int processnum = 5;
//先描述
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;//子进程的名字
};// void slaver(int rfd)
// {
//     while(1)
//     {
//         std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
//         sleep(100000);
//     }
// }
void slaver()
{//0号文件拿任务即可while(1){// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;// sleep(100000);int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待if(n == sizeof(int)){//执行cmdcode对应的任务列表std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;}}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  close(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();//slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}int main()
{srand(time(nullptr)^getpid()^1023); //种一个随机数种子//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程for(int i = 0; i < 5; i++){//1.选择任务int cmdcode = rand() % 20;//2.选择进程//需要负载均衡:轮询或者随机数法int processpos = rand() % channels.size();std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[processpos]._slaverid << std::endl;//3.发送任务write(channels[processpos]._cmdfd, &cmdcode, sizeof(int));sleep(1);}//3.清理收尾sleep(1000);return 0;
}

image-20240119005321469

接下来来加上任务模块

#pragma once
#include <iostream>
#include <vector>typedef void(*task_t)();void task1()
{std::cout << "原神启动!" << std::endl;
}
void task2()
{std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{std::cout << "三国杀启动!" << std::endl;
}
void task4()
{std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task4);tasks->push_back(task3);tasks->push_back(task1);
}

然后是代码

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
const int processnum = 5;
std::vector<task_t> tasks;//先描述
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;//子进程的名字
};// void slaver(int rfd)
// {
//     while(1)
//     {
//         std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
//         sleep(100000);
//     }
// }
void slaver()
{//0号文件拿任务即可while(1){// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;// sleep(100000);int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待if(n == sizeof(int)){//执行cmdcode对应的任务列表std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();}}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  close(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();//slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}int main()
{LoadTask(&tasks);srand(time(nullptr)^getpid()^1023); //种一个随机数种子//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程for(int i = 0; i < tasks.size(); i++){//1.选择任务int cmdcode = i;//2.选择进程//需要负载均衡:轮询或者随机数法int processpos = rand() % channels.size();std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[processpos]._slaverid << std::endl;//3.发送任务write(channels[processpos]._cmdfd, &cmdcode, sizeof(int));sleep(1);}//3.清理收尾    sleep(1000);return 0;
}

运行结果为

image-20240119011216715

最后我们来进行一下写文件的关闭与子进程的退出

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>const int processnum = 5;
std::vector<task_t> tasks;//先描述
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;//子进程的名字
};// void slaver(int rfd)
// {
//     while(1)
//     {
//         std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
//         sleep(100000);
//     }
// }
void slaver()
{//0号文件拿任务即可while(1){// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;// sleep(100000);int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待if(n == sizeof(int)){//执行cmdcode对应的任务列表std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break;}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  close(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();std::cout << "process exit :" << getpid() << std::endl;//slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}void ctrlSlaver(const std::vector<channel>& channels)
{int which = 0;for(int i = 0; i < tasks.size(); i++){//1.选择任务int cmdcode = i;//2.选择进程//需要负载均衡:轮询或者随机数法//int processpos = rand() % channels.size();std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;//3.发送任务write(channels[which]._cmdfd, &cmdcode, sizeof(int));sleep(1);which++;which = which % channels.size();}
}
void QuitProcess(const std::vector<channel>& channels)
{for(const auto& c : channels){close(c._cmdfd);}sleep(5);for(const auto& c : channels){waitpid(c._slaverid,nullptr,0);}sleep(5);
}
int main()
{LoadTask(&tasks);srand(time(nullptr)^getpid()^1023); //种一个随机数种子//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程ctrlSlaver(channels);//3.清理收尾    QuitProcess(channels);//sleep(5);return 0;
}

运行结果为

image-20240119014353826

2.代码

最后我们可以将代码在稍微调整一下,使得我们的代码更加完善,可以通过菜单的方式来实现

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>const int processnum = 5;
std::vector<task_t> tasks;//先描述
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;//子进程的名字
};// void slaver(int rfd)
// {
//     while(1)
//     {
//         std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
//         sleep(100000);
//     }
// }
void slaver()
{//0号文件拿任务即可while(1){// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;// sleep(100000);int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待if(n == sizeof(int)){//执行cmdcode对应的任务列表std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break;}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  close(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();std::cout << "process exit :" << getpid() << std::endl;//slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}
void Menu()
{std::cout << "**********************************************" << std::endl;std::cout << "*********** 1. 原神启动!       ***************" << std::endl;std::cout << "*********** 2. 王者荣耀启动!   ***************" << std::endl;std::cout << "*********** 3. 三国杀启动!     ***************" << std::endl;std::cout << "*********** 4. 金铲铲启动!     ***************" << std::endl;std::cout << "*********** 0. 退出            ***************" << std::endl;std::cout << "**********************************************" << std::endl;std::cout << "Please Enter@" << std::endl;
}
void ctrlSlaver(const std::vector<channel>& channels)
{int which = 0;// for(int i = 0; i < tasks.size(); i++)// {//     //1.选择任务//     int cmdcode = i;//     //2.选择进程//     //需要负载均衡:轮询或者随机数法//     //int processpos = rand() % channels.size();//     std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;//     //3.发送任务//     write(channels[which]._cmdfd, &cmdcode, sizeof(int));//     sleep(1);//     which++;//     which = which % channels.size();// }while(true){int cmdcode = 0;Menu();std::cin >> cmdcode;if(cmdcode <= 0 || cmdcode >= 5) break;cmdcode--;//1.选择任务//2.选择进程std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;//3.发送任务write(channels[which]._cmdfd, &cmdcode, sizeof(int));sleep(1); which++; which = which % channels.size();}
}
void QuitProcess(const std::vector<channel>& channels)
{for(const auto& c : channels){close(c._cmdfd);}//sleep(5);for(const auto& c : channels){waitpid(c._slaverid,nullptr,0);}//sleep(5);
}
int main()
{LoadTask(&tasks);srand(time(nullptr)^getpid()^1023); //种一个随机数种子//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程ctrlSlaver(channels);//3.清理收尾    QuitProcess(channels);//sleep(5);return 0;
}
#pragma once
#include <iostream>
#include <vector>typedef void(*task_t)();void task1()
{std::cout << "原神启动!" << std::endl;
}
void task2()
{std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{std::cout << "三国杀启动!" << std::endl;
}
void task4()
{std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}

3.一个小bug

在我们上面的代码中其实存在一个小bug

如下图所示,当我们的父进程创建子进程的时候,因为我们用的是循环的方式,所以导致了父进程的文件描述符表被拷贝的时候,前面的写端也被拷贝进去了。导致了如下的问题,第一个子进程会被后面的子进程进行写入。

image-20240120170920176

这样带来的问题就是子进程之间也可以相互进行通信了。

那么我们就很好奇了,那这样的话,进程退出的时候不会出现问题吗?

image-20240120172344859

其实,这个代码写的比较巧合,还真的是不会出现问题

因为最后一个管道,他只有一个写端,那么刚好将最后一个写端关闭以后,最后一个读入了0,就会退出,然后他的前一个刚好本来只剩下一个写端,还被退出来了,那么也随之没有写端了。如此循环往复,最终导致所有子进程都是进入僵尸状态了。从而使用进程等待进行回收资源。

如果我们将上面的代码给写成这样子,那么就出现问题了

image-20240120172940337

当我们直接退出的时候,直接卡住了

image-20240120173044242

这是因为,第一个进程由于还有其他的写入端,导致它的管道一直在read时候阻塞了。从而进程无法退出,导致卡住了

如果我们就想要在一个循环里面进行回收,我们可以使用倒着的for循环

image-20240120173725563

image-20240120173916454

不过这个似乎并没有从根本上解决问题,我们应该让每一个进程只有一个写端,这样才是最好的解决方案

如下所示,我们可以加上一个数组用来记录该进程需要关闭的写端文件,然后再子进程刚开始的时候,直接依次关闭即可

image-20240120175231095

image-20240120175206840

4.最终代码

#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>const int processnum = 5;
std::vector<task_t> tasks;//先描述
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;//子进程的名字
};// void slaver(int rfd)
// {
//     while(1)
//     {
//         std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
//         sleep(100000);
//     }
// }
void slaver()
{//0号文件拿任务即可while(1){// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;// sleep(100000);int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待if(n == sizeof(int)){//执行cmdcode对应的任务列表std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break;}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{   std::vector<int> oldfds;for(int i = 0; i < processnum; i++){int pipefd[2]; //临时空间int n = pipe(pipefd);assert(n == 0);(void)n;pid_t id = fork();if(id == 0) //child{  std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {close(fd);std::cout << fd << " ";}std::cout << std::endl;close(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();std::cout << "process exit :" << getpid() << std::endl;//slaver(pipefd[0]);exit(0);}//fatherclose(pipefd[0]);//添加channnel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfds.push_back(pipefd[1]);} 
}void Debug(const std::vector<channel>& channels)
{for(auto& e : channels){std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;}
}
void Menu()
{std::cout << "**********************************************" << std::endl;std::cout << "*********** 1. 原神启动!       ***************" << std::endl;std::cout << "*********** 2. 王者荣耀启动!   ***************" << std::endl;std::cout << "*********** 3. 三国杀启动!     ***************" << std::endl;std::cout << "*********** 4. 金铲铲启动!     ***************" << std::endl;std::cout << "*********** 0. 退出            ***************" << std::endl;std::cout << "**********************************************" << std::endl;std::cout << "Please Enter@" << std::endl;
}
void ctrlSlaver(const std::vector<channel>& channels)
{int which = 0;// for(int i = 0; i < tasks.size(); i++)// {//     //1.选择任务//     int cmdcode = i;//     //2.选择进程//     //需要负载均衡:轮询或者随机数法//     //int processpos = rand() % channels.size();//     std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;//     //3.发送任务//     write(channels[which]._cmdfd, &cmdcode, sizeof(int));//     sleep(1);//     which++;//     which = which % channels.size();// }while(true){int cmdcode = 0;Menu();std::cin >> cmdcode;if(cmdcode <= 0 || cmdcode >= 5) break;cmdcode--;//1.选择任务//2.选择进程std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;//3.发送任务write(channels[which]._cmdfd, &cmdcode, sizeof(int));sleep(1); which++; which = which % channels.size();}
}
// void QuitProcess(const std::vector<channel>& channels)
// {
//     for(const auto& c : channels)
//     {
//         close(c._cmdfd);
//         std::cout << "关闭了一个写端的文件" << std::endl;
//         //sleep(1);
//     }
//     //sleep(5);
//     for(const auto& c : channels)
//     {
//         waitpid(c._slaverid,nullptr,0);
//         //sleep(1);
//     }
//     //sleep(5);
// }
void QuitProcess(const std::vector<channel>& channels)
{for(const auto& c : channels){close(c._cmdfd);waitpid(c._slaverid,nullptr,0);}
}
// void QuitProcess(const std::vector<channel>& channels)
// {
//     for(int i = channels.size() - 1; i >= 0; i--)
//     {
//         close(channels[i]._cmdfd);
//         sleep(1);
//         waitpid(channels[i]._slaverid,nullptr,0);
//         sleep(1);
//     }
// }
int main()
{LoadTask(&tasks);srand(time(nullptr)^getpid()^1023); //种一个随机数种子//在组织std::vector<channel> channels;    //1.初始化InitProcessPool(&channels);//testDebug(channels);//2.开始控制子进程ctrlSlaver(channels);//3.清理收尾    QuitProcess(channels);//sleep(5);return 0;
}
#pragma once
#include <iostream>
#include <vector>typedef void(*task_t)();void task1()
{std::cout << "原神启动!" << std::endl;
}
void task2()
{std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{std::cout << "三国杀启动!" << std::endl;
}
void task4()
{std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}

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

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

相关文章

【Linux】安装n卡驱动以及可能遇到的问题

文章目录 1.换源以及更新2.安装依赖3. 安装n卡驱动独显与核显切换nvidia-settings消失忘记安装依赖无法进入图形化界面的急救命令行无响应办法 1.换源以及更新 目前&#xff0c;换源完全只需要鼠标点点点就可以完成了&#xff0c;打开应用列表里的Software & Updates&…

Spring DI

目录 什么是依赖注入 属性注入 构造函数注入 Setter 注入 依赖注入的优势 什么是依赖注入 依赖注入是一种设计模式&#xff0c;它通过外部实体&#xff08;通常是容器&#xff09;来注入一个对象的依赖关系&#xff0c;而不是在对象内部创建这些依赖关系。这种方式使得对象…

macOS修改默认时区显示中国时间

默认时区不是中国,显示时间不是中国时间 打开终端 ,删除旧区,并复制新时区到etcreb sudo -rm -rf /etc/localtime sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 重启系统后时间显示为中国时间

SQL语句的执行顺序

查询语句语法&#xff1a; SELECT字段列表 FROM表名字段 WHERE条件列表 GROUP BY分组字段列表 HAVING分组后的条件列表 ORDER BY排序字段列表 LIMIT分页参数 执行顺序 #先找到表格 FROM表名字段 WHERE条件列表 GROUP BY分组字段列表 HAVING分组后的条件列表 SELECT字段列表 …

4.C语言——数组

数组 1.什么是数组2.一维数组1.数组定义2.数组赋值3.数组的使用4.数组的存储地址 3.二维数组1.数组定义2.数组赋值3.数组的使用4.数组的存储地址 4.数组名5.数组越界 1.什么是数组 数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量 所有的数组都是由…

【网站项目】329网月科技公司门户网站

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

正则表达式初版

一、简介 REGEXP&#xff1a; Regular Expressions&#xff0c;由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通…

什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(二)

什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(二)。 技术架构是对某一技术问题(需求)解决方案的结构化描述,由构成解决方案的组件结构及之间的交互关系构成。广义上的技术架构是一系列涵盖多类技术问题设计方案的统称,例如部署方案、存储方案、缓存…

Arduino开发实例-INA219 电流传感器驱动

INA219 电流传感器驱动 文章目录 INA219 电流传感器驱动1、INA219 电流传感器介绍2、硬件准备及接线3、代码实现1、INA219 电流传感器介绍 INA219 模块用于同时测量电流和电压。 该模块使用 I2C 通信传输电压和电流数据。 其他特性: 测量精度:1%最大测量电压:26V最大测量电…

Spring:StopWatch

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、输出总耗时 二、输出所有任务的耗时和占比 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、输出总耗时 public void stopWatc…

C++---判断闰年

一.闰年的定义 闰年是指在公历中&#xff0c;年份可以被4整除但不能被100整除的年份&#xff0c;或者可以被400整除的年份。简单来说&#xff0c;闰年是一个比平年多出一天的年份&#xff0c;即2月有29天。闰年的目的是校准公历与地球公转周期的差异&#xff0c;确保时间计算的…

自然语言推断:注意力之注意(Attending)

注意&#xff08;Attending&#xff09; 第一步是将一个文本序列中的词元与另一个序列中的每个词元对齐。假设前提是“我确实需要睡眠”&#xff0c;假设是“我累了”。由于语义上的相似性&#xff0c;我们不妨将假设中的“我”与前提中的“我”对齐&#xff0c;将假设中的“累…

ARM体系架构

1. 计算机组成 交叉开发: 程序的编写 编译 在 PC机上(宿主机) 但 运行在 开发板(目标机) 嵌入式开的的特点: 开发环境的不同: 交叉开发环境 以应用为中心, 围绕实际功能设计 软件和硬件 量体裁衣 1.1 计算机的基本组成部分: 输入设备 输出设备 存储器 运算器 控制器 总…

为什么 HTTPS 协议能保障数据传输的安全性?

HTTP 协议 在谈论 HTTPS 协议之前&#xff0c;先来回顾一下 HTTP 协议的概念。 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议&#xff0c;它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯&#xff0c;目前协议由之前的 RFC 2616 拆…

SpringBoot整合Dubbo和Zookeeper分布式服务框架使用的入门项目实例

文章目录 SpringBoot整合Dubbo和Zookeeper分布式服务框架使用的入门项目实例Dubbo定义其核心部分包含: 工作原理为什么要用dubbo各个节点角色说明&#xff1a;调用关系说明&#xff1a; dubbo为什么需要和zookeeper结合使用&#xff0c;zookeeper在dubbo体系中起到什么作用&…

谷歌浏览器通过network模拟HTTP中的GET/POST请求获取response

1、F12打开network选中需要模拟的方法Copy->Copy as fetch 2、通过AI帮你进行转换一下调用格式 原代码 fetch("https://mp.amap.com/api/forward/aggregate?mtop.alsc.kbt.intergration.toolkit.call.queryCallBlockInfo", {"headers": {"acce…

Django开发_14_后台管理及分页器

一、后台管理 &#xff08;一&#xff09;登录 http://127.0.0.1:8000/admin/ &#xff08;二&#xff09;创建超级用户 manage.py createsuperuser &#xff08;三&#xff09;注册模型 admin.py&#xff1a; models [model1&#xff0c;model2&#xff0c;model3 ]ad…

浅析Java中volatile关键字

认识volatile关键字 Java中的volatile关键字用于修饰一个变量&#xff0c;当这个变量被多个线程共享时&#xff0c;这个变量的值如果发生更新&#xff0c;每个线程都能获取到最新的值。volatile关键字在多线程环境下还会禁止指令重排序&#xff0c;确保变量的赋值操作按照代码的…

VS Code + Python + Selenium 自动化测试基础-01

VS Code Python Selenium 自动化测试基础-01 让我们来讲一个故事为什么要写自动化开发前的准备工作牛刀小试开常用的web DriverAPI-定位元素id定位&#xff1a;find_element_by_id()name 定位&#xff1a;find_element_by_name()class 定位&#xff1a;find_element_by_class…

机器视觉技术与应用实战(平均、高斯、水平prewitt、垂直prewitt、水平Sobel、垂直Sobel、拉普拉斯算子、锐化、中值滤波)

扯一点题外话&#xff0c;这一个月经历了太多&#xff0c;接连感染了甲流、乙流&#xff0c;人都快烧没了&#xff0c;乙流最为严重&#xff0c;烧了一个星期的38-39度&#xff0c;咳嗽咳到虚脱。还是需要保护好身体&#xff0c;感觉身体扛不住几次连续发烧&#xff01;&#x…