Linux的进程间通信 && 命名和匿名管道 && 进程池(重要)

目录

进程间通信IPC

管道

匿名管道

pipe函数

命名管道 

mkfifo函数

unlink函数

进程间通信(仅父子间)

进程间通信(不受血缘限制)

命名管道类

服务端(读端)

客户端(写端)

有名管道和匿名管道的区别

进程池

ProcessPool.cpp

Task.cpp


进程间通信IPC

基本概念:指不同的进程之间交换数据和信息的机制。由于OS中每个进程都有自己独立的地址空间,无法直接访问其他进程的内存,因此需要通过操作系统提供的 IPC(进程间通信) 机制来实现不同进程间的数据传递和信息共享

IPC的前提:要通信的两个或多个进程可以看到同一份OS中的资源

注意事项:fork函数创建出的子进程继承父进程不属于进程间通信

管道

基本概念:用于支持两个有关联的程序在本地互相传输信息(是最早使用的进程间通信的方式之一,其余的还有消息队列、共享内存、信号量)管道分为有名管道和匿名管道

管道使用的四种情况:

1、管道内部为空,不具备读取条件,读进程会被阻塞(wait)等到管道不为空时才会读取

2、管道写满但rfd不读取,此时写进程会被阻塞,等到管道不为满时才会继续写入

3、管道一直在读但wfd关闭,此时读端的read函数会返回0,表示读取到了文件结尾

4、rfd关闭但wfd一直写入,此时wfd会被OS直接用13号信号杀掉,因为OS判断该进程异常

(用rfd表示读端进程,用wfd表示写段进程)

注意事项:

1、父子进程的file struct中的fd指向的都是内核中同一个struct file的结构体

2、管道中的内容不会刷新到磁盘中

3、当管道的所有文件描述符关闭后,管道中的数据会被释放,无法持久化

4、不论是命名管道还是匿名管道都是向内核级文件缓冲区申请内存,只不过前者将这段申请到的内存命名了,本地所有进程都可以看见,而匿名的因为只能是有血缘关系的进程间的通信所以不需要指明

关于内核级缓冲区的解释:为了避免读写文件时频繁的对磁盘进行操作,OS会先将要读写的文件从磁盘拿到内核级文件缓冲区(即页缓存,类似于高并发内存池中的PageCache)中,并定期将内核级文件缓冲区中的内容重新刷新到磁盘中的指定文件中

问题:为什么进程都默认打开三个标准流的文件描述符0、1、2?

解释:因为所有的进程都是bash的子进程,当bash打开其子进程也就打开了

问题:为什么子进程主动close(0或1或2)不影响父进程继续使用显示器文件呢?

解释:struct file中存在一个内存级的引用计数,父子进程同时指向一个struct file则该引用计数为2,close子进程的某个标准流文件时,只会将该引用计数减一,父进程依然可以访问

问题:如何实现父进程读文件,子进程写文件?

解释:父进程仍然打开3号文件描述符close(4),子进程仍然打开4号文件描述符close(3)

问题:父子既然要关闭不需要的fd,为何之前还要打开?可以不关闭吗?

解释:①为了让子进程继承,如果开始时父进程只打开一个3后续子进程还要再关闭3再打开4,还不如父子进程都打开3和4按照实际情况再进行关闭 可以,但可能会造成父子进程同时写入,同时还有文件描述符泄露问题,当子进程通过 fork() 创建时,默认会继承父进程所有打开的文件描述符。如果子进程没有关闭不需要的文件描述符,那么这些文件描述符就会一直保留在子进程中,尽管子进程可能并不需要使用它们。这会导致文件描述符泄漏,即文件描述符数量被不必要地占用,浪费系统资源。

匿名管道

基本概念:一种进程间通信机制。为两个进程提供了单向的数据传输通道,一端用于写入数据,另一端用于读取数据,仅限于有血缘关系的进程间通信如果想要双向通信可以使用两个匿名管道

注意事项:

1、当向匿名管道中读写的文件描述符均关闭,则表示释放该匿名管道的使用权(可使用的内存级文件缓冲区被收回),下一次再次进行读写时还要再次调用pipe函数进行申请rfd和wfd,以及可以使用的匿名管道

2、管道文件在通信时是面向字节流的,因此读写次数不一定一一匹配。也就是说,管道中写入的字节数量与读取的字节数量并不总是严格对应,可能出现多次写入被一次读取,或一次写入被多次读取的情况

pipe函数

函数原型:int pipe(int pipefd[2]);

  • pipefd[2]:输出型参数,第一个元素表示读端的文件描述符rfd第二个元素表示写端的文件描述符wfd,由OS自行填写,但是需要先定义出来(跟其它输出型参数的要求一样),程序运行时会自动分配可以使用的wfd和rfd的值,然后将这两个值分配给进行进程间通信的两个进程

包含头文件:<unistd.h>

返回值:创建成功返回0,创建失败返回-1

功能:在OS中向内核级文件缓冲区申请了一块用于进程间通信的区域(即匿名管道)

命名管道 

基本概念:不仅限于有血缘关系间进程的通信,适用于所有进程间通信(只要能看到),创建时需要提供一个文件路径(该文件当前不存在,如果存在mkfifo函数会报错),本质也是向内核级缓冲区申请一块内存,通信时也是使用这块内存

mkfifo函数

函数原型:int mkfifo(const char *pathname, mode_t mode);

  • pathname:命名管道文件的所属路径名

  • mode:设置对管道文件的访问权限

包含头文件: <sys/types.h>  和  <sys/stat.h>

返回值:成功创建返回0,创建失败返回-1

功能:在文件系统中创建一个命名管道

注意事项:创建管道文件时也会依据提供的路径名和文件名在磁盘中真实的创建该文件(即命名管道文件在磁盘上有文件名和路径​​​​​​​),用于进程间通信的入口(通信的标识符),但它不占用磁盘空间来存储实际数据,使用完后要用unlink函数进行清除,否则一直存在

unlink函数

函数原型:int unlink(const char *pathname);

  • pathname:要删除的命名管道的路径

包含头文件:<unistd.h>

返回值:成功删除返回0,删除失败返回-1

功能:删除磁盘中指定的管道文件

注意事项:仅删除文件在文件系统中的名称(类似于rm test.txt),而不是删除文件的内容(test.txt文件的内容不会被一起删除)如果某个命名管道正被某个进程使用(即还有打开的文件描述符指向它),文件内容会一直保留,直到最后一个文件描述符关闭时,文件内容才会被真正删除

进程间通信(仅父子间)

#include <iostream>
#include <unistd.h>
#include <cerrno>  //c++版本的errno.h
#include <cstring> //c++版本的string.h
#include <sys/wait.h>
#include <sys/types.h>
#include <string>

// 携带发送的信息
std::string getOtherMessage()
{
    // 获取要返回的信息
    static int cnt = 0; // 计数器
    std::string messageid = std::to_string(cnt);
    cnt++;                    // 每使用一次计数器就++
    pid_t self_id = getpid(); // 获取当前进程的pid
    std::string stringpid = std::to_string(self_id);

    std::string message = " my messageid is : ";
    message += messageid;
    message += " my pid is : ";
    message += stringpid; // 逐渐向要传回的string字符串中追加要返回的信息

    return message;
}

// 子进程进行写入
void ChildProcessWrite(int wfd)
{
    std::string message = "father, I am your child process!";
    while (true)
    {
        std::string info = message + getOtherMessage(); // 子进程尝试向父进程传递的所有信息
        write(wfd, info.c_str(), info.size());          // write函数传入的字符串需要是c语言格式的,c_str将string字符串变为c语言格式的字符串
        sleep(1);                                       // 让子进程写慢一点,这样父进程就不会一直读并打印在显示器上
    } // write是由操作系统提供的接口,而操作系统又是C语言编写的,所以后续学习中可能会碰到c语言的接口和c++的接口混合使用的情况
} // info最后有/0但是文件不需要

const int size = 1024; // 定义父进程可以读取的数组大小

// 父进程进行读取
void FatherProcessRead(int rfd)
{
    char inbuffer[size]; // 普通的c99标准不支持变长数组,但是这里使用的是gnb的c99标准,gun的c99标准支持变长数组
    while (true)
    {
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer)); // 因为文件不需要\0,所以读取管道中内容到缓冲区时可以少读取一个并将/0变为0
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << "父进程获取的消息: " << inbuffer << std::endl;
        }
    }
}

int main()
{
    // 1、创建管道
    int pipefd[2];
    int n = pipe(pipefd); // 输出型参数,pipefd[0] = rfd,pipefd[1] = wfd
    if (n != 0)
    {
        std::cerr << "errno" << errno << ":" << "errstring" << strerror(errno) << std::endl;
        return 1;
    }

    // pipefd[0]即读端fd,pipefd[1]即写端fd
    std::cout << "pipefd[0] = " << pipefd[0] << ", pipefd[1] = " << pipefd[1] << std::endl;

    sleep(1); // 便于看到管道创建成功

    // 2、创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "子进程关闭不需要的fd,准备发消息了" << std::endl;
        sleep(1); // 便于感受到发消息的过程

        // 子进程---写端
        // 3、关闭不需要的fd
        close(pipefd[0]);

        ChildProcessWrite(pipefd[1]); // 子进程的写函数

        close(pipefd[1]); // 完成通信后也将子进程的写端关闭
        exit(0);
    }
    std::cout << "发进程关闭不需要的fd,准备收消息了" << std::endl;
    sleep(1); // 便于感受到收消息的过程

    // 父进程---读端
    close(pipefd[1]);
    FatherProcessRead(pipefd[0]); // 父进程的读函数
    close(pipefd[0]);             // 完成通信后也将子进程的读端关闭

    pid_t rid = waitpid(id, nullptr, 0);
    if (rid > 0)
    {
        std::cout << "wait child process done" << std::endl;
    }

    return 0;
}

  • 开始读写前,父子进程要关闭不需要的文件描述符,读取完毕后也要关闭使用完的文件描述符

补充:因为可以用write和read读取匿名管道,所以匿名管道也是文件

进程间通信(不受血缘限制)

命名管道类

功能负责管道的创建和销毁

#pragma once

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string comm_path = "./myfifo"; // 提供文件的路径(相当于在当前目录下生成一个叫myfifo的命名管道),string字符串在使用时需要转换为c类型的字符串

#define DefaultFd -1 // 将-1用于标识管道的默认fd(有进程打开管道时再分配新的fd)

#define Creater 1 // 将1用于标识管道的申请者
#define User 2    // 将2用于标识管道的使用者

#define Read O_RDONLY  // 将Read用于标识只读方式
#define Write O_WRONLY // 将Write用于标识只写方式

#define BaseSize 4096 // 将4096作为读写时的最大字节数

class NamePiped
{
private:
    bool OpenNamedPipe(int mode) // 根据不用用户的不同需求为该用户分配不同的文件描述符
    {
        _fd = open(_fifo_path.c_str(), mode); // 若mode是Read,则_fd是rfd、若mode是Read,则_fd是wfd
        if (_fd < 0)
            return false; // 分配失败返回false
        return true;      // 分配成功返回true
    }

public:
    NamePiped(const std::string &path, int who) // 依据文件路径和身份信息构建管道
        : _fifo_path(path), _id(who), _fd(DefaultFd)
    {
        if (_id == Creater) // 只有申请者才能创建管道
        {
            int res = mkfifo(_fifo_path.c_str(), 0666); // 创建管道
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "creater create named pipe" << std::endl;
        }
    }

    // 以读方式打开管道
    bool OpenForRead()
    {
        return OpenNamedPipe(Read);
    }

    // 以写方式打开管道
    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }

    // 读管道的方式
    int ReadNamedPipe(std::string *out) // 输出型参数
    {
        char buffer[BaseSize]; // 一次能读取到的最大内容
        int n = read(_fd, buffer, sizeof(buffer));//依据rfd从管道中读取
        if (n > 0)
        {
            buffer[n] = 0;
            *out = buffer; //*out指向读取到的内容
        }
        return n;
    }

    // 写管道的方式
    int WriteNamedPipe(const std::string &in)
    {
        return write(_fd, in.c_str(), in.size()); // 依据wfd读取
    }

    ~NamePiped()
    {
        if (_id == Creater) // 只有申请者才能创建管道
        {
            int res = unlink(_fifo_path.c_str());
            if (res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if (_fd != DefaultFd) // 关闭写端fd
            close(_fd);
    }

private:
    const std::string _fifo_path; // 提供的文件路径
    int _id;                      // 标识使用者的身份信息
    int _fd;                      // 标识文件描述符
};

服务端(读端)

功能:申请并创建管道,以及最后管道的销毁,读取管道中的内容

#include "namedPipe.hpp"

// server read: 管理命名管道的整个生命周期
int main()
{
    NamePiped fifo(comm_path, Creater);
    // 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开
    // 进程同步
    if (fifo.OpenForRead()) // 调用以读方式打开管道函数
    {
        std::cout << "server open named pipe done" << std::endl;

        sleep(3);
        while (true) // 循环写入数据
        {
            std::string message;
            int n = fifo.ReadNamedPipe(&message);
            if (n > 0)
            {
                std::cout << "Client Say> " << message << std::endl;
            }
            else if (n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }

    return 0;
}

客户端(写端)

功能:向管道中写入消息

#include "namedPipe.hpp"

// write
int main()
{
    NamePiped fifo(comm_path, User); // 为写端提供使用管道的方法(即便有些根本用不上)
    if (fifo.OpenForWrite())         // 调用以写方式打开管道函数
    {
        std::cout << "client open namd pipe done" << std::endl; // 客户端持续输入
        while (true)
        {
            std::cout << "Please Enter> ";
            std::string message;
            std::getline(std::cin, message); // 将获取的命令行字符串作为message写入管道
            fifo.WriteNamedPipe(message);
        }
    }
    return 0;
}
  • 输入型参数:只读,不修改
  • 输出型参数:由函数初始化或修改,用于返回数据
  • 输入输出型参数:由调用者初始化,由函数修改,并返回修改后的结果

流程描述:

  1. Server创建命名管道类,并以读方式打开(同时获取一个rfd,这个rfd在Client创建的命名管道类中是存放在_fd中的,后续调用OpenForWrite时会用到)等待Client向管道中写入数据
  2. Client也创建一个命名管道类,但并不会创建一个新的命名管道只是为了后续使用命名管道中的操作函数,之后以写方式打开(同时获取一个wfd,也是存放在它的命名管道类的_fd中,后续调用OpenForWrite时会用到)开始向管道中写入数据

有名管道和匿名管道的区别

进程池(有待重复观看)

基本概念:提前创建多个用于执行任务的子进程,当父进程派发任务时子进程去处理父进程的任务,处理完成后继续阻塞等待

ProcessPool.cpp

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

// 管道
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string &name)
        : _wfd(wfd), _subprocessid(id), _name(name)
    {
    }

    ~Channel()
    {
    }

    // 获取写端的wfd、目标子进程的pid,当前管道名
    int GetWfd() { return _wfd; }

    //获取进程id
    pid_t GetProcessID() { return _subprocessid; }

    std::string GetName() { return _name; }

    void CloseChannel() // 关闭连接当前管道的wfd
    {
        std::cout << "关闭当前进程连接到管道的wfd: " << _wfd << std::endl;
        close(_wfd);
    }

    void wait() // 子进程阻塞等待
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0); // (阻塞等待的子进程pid,指向子进程的退出信息(因为没有写就直接设置为空指针),选择阻塞等待的方式)返回值是阻塞成功的子进程的pid
        if (rid > 0)
        {
            std::cout << "pid = " << rid << " 的子进程变为阻塞等待 " << std::endl; // 打印阻塞成功的子进程的pid
            std::cout << std::endl;
        }
    }

private:
    int _wfd;
    pid_t _subprocessid;
    std::string _name;
};

// 形参命名规范
// const & 修饰的应该是一个输入型参数
//& 修饰的应该是一个输入输出型参数
//* 修饰的应该是一个输出型参数

// 创建管道和进程池
void CreatChannelAndSub(int num, std::vector<Channel> *channels, task_t task) // task_t task是回调函数,当子进程执行fork时会去回调指定好的任务文件中的work函数,实现了任务文件和进程文件间的解耦
{
    for (int i = 0; i < num; i++) // 循环创建子进程和对应的管道
    {
        // 1、创建管道
        int pipefd[2] = {0};  // 存放读写端文件描述符的数组(该数组在每次循环时都重置)
        int n = pipe(pipefd); // 每次循环时都由OS向数组中写入分配给新建管道的读端和写端的文件描述符(OS依据会fd的占用情况分配不同的fd给新的管道)
        if (n < 0)
            exit(1); // 创建管道失败进程退出

        // 2、创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            // 处理第二次创建管道时的子进程中还有指向第一个管道的rfd
            if (!channels->empty()) // 管道数组不为空,即到了第二次创建管道时才会执行该判断语句
            {
                for (auto &channel : *channels) // 循环遍历之前的管道并拿到这些管道的rfd,然后关闭当前进程的这些rfd
                {
                    channel.CloseChannel();
                }
            }
            std::cout << std::endl;

            sleep(5);

            // 子进程
            close(pipefd[1]); // 关闭子进程的wfd
            // work(pipefd[0]);  // 子进程等待并处理父进程派发的任务

            // dup2(pipefd[0],0);//子进程不仅可以从管道中,还可以从标准输入中获取任务码
            // work();//我们不给work传rfd就可以断绝子进程从管道中获取任务码,这样就进一步完成了管道和子进程间逻辑的解耦

            dup2(pipefd[0], 0); // 子进程不仅可以从管道中,还可以从标准输入中获取任务码,这种方法使得子进程可以像处理标准输入一样处理来自管道的数据,从而提高了代码的通用性和可移植性。
            task();             // 将work也视为一个任务

            close(pipefd[0]); // 关闭子进程的rfd
            exit(0);          // 子进程退出
        }

        // 3、父进程构建管道名
        std::string Channel_name = std::to_string(i) + "号 Channel"; // 每次循环i+1,管道名即为i号 Channel

        close(pipefd[0]); // 关闭父进程的rfd

        // 向数组中尾插管道
        channels->push_back(Channel(pipefd[1], id, Channel_name)); // 会向当前管道写入父进程的wfd,当前管道对应的子进程pid,当前管道
    }
}

// 检测进程池和管道是否创建成功
void TestForProcessPoolAndSub(std::vector<Channel> &channels)
{
    std::cout << "=========================================================" << std::endl;
    std::cout << "   管道名    " << "   管道对应的子进程pid   " << "   会向当前管道写入的wfd   " << std::endl;
    for (auto &Channel : channels)
    {
        std::cout << " " << Channel.GetName() << "          " << Channel.GetProcessID() << "                  " << Channel.GetWfd() << std::endl;
    }
    std::cout << "=========================================================" << std::endl;
}

// 获取一个管道下标(利用取模 + static变量 在0~channelnum间循环)(使得每个管道都会被使用到的轮询方案)
int NextChannel(int channelnum)
{
    static int next = 0;
    int index = next;
    next++;
    next %= channelnum;
    return index;
}

// 发送任务
void SendTaskCommand(Channel &channels, int taskcommand) // 此时是向数组中的一个管道派发任务,所以形参应该是一个管道类类型的对象
{
    write(channels.GetWfd(), &taskcommand, sizeof(taskcommand)); // 像指定的wfd中写入(OS会通过该wfd找到对应的管道,这里本质上就是向管道中写入),要写入的任务码,任务码的大小
} // 这里不是要截取任务码所存放地址的前四个字节,而是获取任务码本身,别搞错了

// 向子进程派发一次任务(也叫通过管道控制子进程,因为只要向某一个管道中派发任务后该管道对应的子进程就可以接收到该任务,二者的连接关系提前已经建立好了)
void CtrlProcessOnce(std::vector<Channel> &Channels)
{
    sleep(1);
    // 1、选择一个任务(获取一个任务码,本质是获取一个函数指针)
    int taskcommand = SelectTask();

    // 2、选择一个管道进行任务的派发
    int channel_index = NextChannel(Channels.size());

    // 3、发送任务
    SendTaskCommand(Channels[channel_index], taskcommand); // 向指管道发送任务码,因为管道和子进程建立了关联,向管道中输入内容时子进程在自己的work函数中就会读取到管道中的任务码,然后子进程就会依据该任务码去执行相应的任务

    std::cout << "分配的随机任务码为:" << taskcommand << " 派发给的管道名为: "
              << Channels[channel_index].GetName() << " 处理任务的子进程pid为: " << Channels[channel_index].GetProcessID() << std::endl;
}

// 向子进程派发任务
void CtrlProcess(std::vector<Channel> &Channels, int time = -1) // 默认一直向子进程派发任务
{
    if (time > 0)
    {
        while (time--)
        {
            CtrlProcessOnce(Channels);
        }
    }
    else
    {
        while (true)
        {
            CtrlProcessOnce(Channels);
        }
    }
}

// 回收管道和子进程(释放而不是等待)
void CleanUpChannelAndSubProcess(std::vector<Channel> &Channels)
{
    for (auto &i : Channels)
    {
        i.CloseChannel(); // 先关闭写端wfd
        i.wait();         // 然后让子进程阻塞等待
    }
}

// 创建进有五个进程的进程池,在命令行中的命令行字符串是./processpool 5,一共有命令行字符串的数量应该为2时才能进行创建进程池
int main(int agrc, char *argv[])
{

    if (agrc != 2) // 命令行参数不为2那么就报错并返回
    {
        std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;
        return 1;
    }

    int num = std::stoi(argv[1]); // 将argv数组中获取到的命令行字符串经stoi函数转为整型并赋值给num,num表示要进程池中子进程的个数

    LoadTask(); // 加载任务
    std::cout << "加载任务成功..." << std::endl;

    std::vector<Channel> Channels; // 对管道的处理变成了对数组中Channels对象的增删查改

    // 1、创建管道和进程池
    CreatChannelAndSub(num, &Channels, work); // 规定子进程创价后会回调work函数

    TestForProcessPoolAndSub(Channels); // 检测进程池和管道是否创建成功(到这里所有管道和子进程的连接关系已经建立完成)
    std::cout << "创建并关联子进程与管道成功..." << std::endl;
    std::cout << std::endl;

    // 2、向子进程派发任务
    CtrlProcess(Channels, 4);
    sleep(2);
    std::cout << "子进程处理任务成功..." << std::endl;
    std::cout << std::endl;

    // 3、回收管道和子进程
    CleanUpChannelAndSubProcess(Channels);
    std::cout << "回收管道和子进程成功.." << std::endl;
    std::cout << std::endl;
    return 0;
}

Task.cpp

#pragma once
#include <iostream>
#include <ctime>
#include <cstdlib> //c++风格的c语言的stdlib.h头文件
#include <sys/types.h>
#include <unistd.h>

#define TaskNum 3 // 定义要处理的任务类型个数

typedef void (*task_t)(); // task_t 函数指针类型(task_t是一个类型不是一个指针)

task_t tasks[TaskNum]; // 创建一个task_t函数指针类型的数组tasks,数组中存放的都是函数指针

// 打印任务
void Print()
{
    std::cout << "I am a print task" << std::endl;
}

// 下载任务
void DownLoad()
{
    std::cout << "I am a DownLoad task" << std::endl;
}

// 刷新任务
void Flush()
{
    std::cout << "I am a Flush task" << std::endl;
}

// 加载任务(将任务放入函数指针数组中)
void LoadTask()
{
    srand(time(nullptr) ^ getpid() ^ 17777); // 依据时间戳 与 当前进程的pid亦或的结果使得“种子”更加的随机,当然也可以再亦或上其它内容
    tasks[0] = Print;                        // 第一个函数指针指向打印任务
    tasks[1] = DownLoad;                     // 第二个函数指针指向下载任务
    tasks[2] = Flush;                        // 第三个函数指针指向刷新任务
}

// 执行任务
void ExcuteTask(int number)
{
    if (number < 0 || number > 2)
        return;
    tasks[number](); // 根据传入的任务码确定要调用的函数
}

// 选择任务码
int SelectTask()
{
    return rand() % TaskNum; // 返回随机的任务码
}

// // 版本一:
// //  子进程处理派发的任务(子进程会从依据rfd从管道中拿到任务码)
// void work(int rfd)
// {
//     // 子进程循环等待
//     int i = 1;
//     while (1)
//     {
//         int command = 0;
//         int n = read(rfd, &command, sizeof(command)); // OS会依据rfd帮助子进程获取与它关联的管道中的内容
//         if (n == sizeof(int))
//         {
//             std::cout << "pid = " << getpid() << " 的子进程正在执行任务" << std::endl;
//             ExcuteTask(command); // 依据任务码执行任务
//             std::cout << std::endl;
//         }
//         else if (n == 0) // 读端读取不到内容时结束子进程的work
//         {
//             std::cout << "pid = " << getpid() << " 的子进程读取不到内容了" << std::endl;
//             break;
//         }
//     }
// }

// 版本二:
// 子进程的任务
void work()
{
    // 子进程循环处理任务
    int i = 1;
    while (1)
    {
        int command = 0;
        int n = read(0, &command, sizeof(command)); // OS会依据rfd帮助子进程获取与它关联的管道中的内容
        std::cout << std::endl;
        if (n == sizeof(int))
        {
            std::cout << "pid = " << getpid() << " 的子进程正在执行任务" << std::endl;
            ExcuteTask(command); // 依据任务码执行任务
            std::cout << std::endl;
            sleep(1);
        }
        else if (n == 0) // 读端读取不到内容时结束子进程的work
        {
            std::cout << "pid = " << getpid() << " 的子进程读取不到内容了" << std::endl;
            break;
        }
    }
}

~over~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值