【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 2

char 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.txt
            pos++;
            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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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; //子进程的PID
    std::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);
        }
        //father
        close(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);
    //test
    Debug(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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】—— 如何创建SpringBoot工程

SpringBoot简化了Spring应用的初始搭建和开发过程。 工程创建 新建模块 出现java: 错误: 无效的源发行版&#xff1a;18这样的错误&#xff0c; 修改pom.xml文件 出现以下信息&#xff0c;即运行成功 修改默认端口 创建application.yml文件 内容&#xff1a; server:port:…

【没学过编程语言,想要做一款游戏应该怎么做?】

*** 【没学过编程语言&#xff0c;想要做一款游戏应该怎么做&#xff1f;】 想让你的创意成为像《堡垒之夜》《原神》这样引爆式的热门游戏吗&#xff1f; 想制作一个能与《我的世界》《模拟城市》一决高下的畅销游戏吗&#xff1f; 即使你手头并没有复杂的代码能力&#xf…

知识图谱KG+大模型LLM

LLM-based KG KnowLM OpenSPGKG-based RAG 基本原理 从query出发的语义解析 pre-LLM方法 思想&#xff1a;直接将问题解析为对应的逻辑表达式&#xff0c;然后到知识图谱中查询。 方法&#xff1a;通常包含逻辑表达式、语义解析算法、语义解析模型训练三部分。一般步骤是将问句…

【51单片机Keil+Proteus8.9+ADC0804】ADC实验 模拟转数字实验

一、实验名称 ADC实验 模拟转数字实验 二、设计思路 电路设计 1.选用AT89C51单片机作为电路核心单元&#xff0c;外接8位单通道AD转换器ADC0804芯片和LM016L显示器以及滑动变阻器等其它常用元器件构成电路。 2.将ADC0804芯片的控制引脚RD,WR,INTR接到AT89C51芯片对应引脚&…

管理信息系统知识点复习

目录 一、名词解释题1.企业资源规划(ERP)2.面向对象方法&#xff1a;3.电子健康&#xff1a;4.供应链5.数据挖掘6.“自上而下”的开发策略&#xff1a;7.业务流程重组8.面向对象&#xff1a;9.决策支持系统10.聚类11.集成开发环境&#xff1a;12.供应商协同13.数据仓库14.深度学…

pytorch集智-6手写数字加法机-迁移学习

1 概述 迁移学习概念&#xff1a;将已经训练好的识别某些信息的网络拿去经过训练识别另外不同类别的信息 优越性&#xff1a;提高了训练模型利用率&#xff0c;解决了数据缺失的问题&#xff08;对于新的预测场景&#xff0c;不需要大量的数据&#xff0c;只需要少量数据即可…

STM32407用汇顶的GT911触摸芯片调试实盘

这个配置很关键 代码 #include "stm32f4xx.h" #include "GT9147.h" #include "Touch.h" #include "C_Touch_I2C.h" #include "usart.h" #include "delay.h" #include "LCD.h" #incl…

Java String基础学习

目录 1、String的构造方法 2、String内存模型 3、字符串的比较 4、字符串的练习 1、用户登录系统 2、遍历字符串 3、统计字符次数 4、拼接字符串 5、字符串的反转 6、金额转换 7、手机号屏蔽 * 8、身份证信息查看 9、敏感词替换 5、StringBuilder 1、概念及练习…

新手也能看懂的【前端自动化测试入门】!

前言 最近在网上搜索前端自动化测试相关的文档&#xff0c;但是发现网上的文章都是偏使用&#xff0c;没有把一些基础概念说清楚&#xff0c;导致后续一口气遇到一些karma、Jasmine、jest、Mocha、Chai、BDD等词汇的时候很容易一头雾水&#xff0c;这次一方面整理一下收获的知…

YOLOv8改进 | 进阶实战篇 | 利用YOLOv8进行视频划定区域目标统计计数

一、本文介绍 Hello,各位读者,最近会给大家发一些进阶实战的讲解,如何利用YOLOv8现有的一些功能进行一些实战, 让我们不仅会改进YOLOv8,也能够利用YOLOv8去做一些简单的小工作,后面我也会将这些功能利用PyQt或者是pyside2做一些小的界面给大家使用。 在开始之前给大家推…

解决Spring Boot跨域问题(配置JAVA类)

什么是跨域问题 跨域问题指的是不同端口之间&#xff0c;使用 ajax 无法相互调用的问题。跨域问题本质是浏览器的一种保护机制&#xff0c;它是为了保证用户的安全&#xff0c;防止恶意网站窃取数据。 比如前端用的端口号为8081&#xff0c;后端用的端口号为8080&#xff0c;后…

Linux下安装docker

1、查看系统版本 Docker支持64位版本的CentOS 7和CentOS 8及更高版本&#xff0c;它要求Linux内核版本不低于3.10。查看Linux版本的命令这里推荐两种&#xff1a;lsb_release -a或cat /etc/redhat-release。 显然&#xff0c;当前Linux系统为CentOS7。再查一下内核版本是否不低…

SpringBoot+dynamic-datasource实现多数据源(msyql、sqlserver、postgresql)手动切换

场景 SpringBootMybatisPlusdynamic-datasources实现连接Postgresql和mysql多数据源&#xff1a; SpringBootMybatisPlusdynamic-datasources实现连接Postgresql和mysql多数据源-CSDN博客 上面实现通过注解和配置文件的方式去进行多数据源操作。 如果业务需求&#xff0c;比…

Ubuntu安装最新版Docker和Docker-Compose

ubuntu环境搭建专栏&#x1f517;点击跳转 Ubuntu系统环境搭建&#xff08;十&#xff09;——Ubuntu安装最新版Docker和Docker Compose 文章目录 Ubuntu系统环境搭建&#xff08;十&#xff09;——Ubuntu安装最新版Docker和Docker Compose1.添加Docker库1.1 安装必要的证书并…

PostgreSQL命令大全

文章目录 连接与退出数据库操作表操作外键约束视图操作存储过程与函数权限管理事务管理查询优化与分析数据类型转换分区表操作复制与备份恢复 PostgreSQL是一个功能强大的开源关系型数据库管理系统&#xff0c;以下是一些基本且常用的命令按功能分类&#xff1a; 连接与退出 连…

【前端设计】流光按钮

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 css body{height: 100vh;display: flex;justify-content: center;align-items: center;background…

HarmonyOS鸿蒙学习基础篇 - 项目目录和文件介绍

├── hvigor //存储购置信息的文件&#xff0c;主要用于发布打包 ├── idea //开发工具相关配置可忽略 ├── AppScope //工程目录 全局公共资源存放路径 │ └── resources │ │ └── base │ │ │ └── element //常亮存放 │ │ │ …

Windows安装WSL2精简版教程

文章目录 一、安装WSL二、更改WSL的存放路径/备份WSL三、安装WSL Terminall四、WSL界面&#xff1a;xlaunch五、WSL1升级WSL2六、WSL2与VMware兼容问题七、更改手动导入的wsl的默认登录用户参考 一、安装WSL 步骤1 - 启用适用于 Linux 的 Windows 子系统&#xff1a; 需要先启…

格局打开!前端未死,只是要求变高了

本文笔者会从以下几个方面分享&#xff0c;希望能够帮助正在迷茫的前端小伙伴提供一点思路&#xff01; 逛技术博客 不局限框架 全栈工程师兴起 关注前沿 写技术文章 录制前端视频 总结 2024年了&#xff0c;没工作的找到工作了吗&#xff1f;有工作的加薪了吗&#xff1f;加薪…

UI设计中的插画运用优势(上)

1. 插画是设计的原创性和艺术性的基础 无论是印刷品、品牌设计还是UI界面&#xff0c;更加风格化的插画能够将不同的风格和创意加入其中&#xff0c;在激烈的竞争中更容易因此脱颖而出。留下用户才有转化。 2. 插画是视觉触发器&#xff0c;瞬间传达大量信息 我们常说「一图胜千…