【Linux】进程池

​​​​​​​

通过父进程 fork 多个子进程,并且给每个子进程分配一个管道来完成不同任务的过程:

cpp 复制代码
#include <iostream>
#include <ctime>
#include <vector>
#include <sys/wait.h>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <string>



/////////////////////////////////////////////////子进程完成的任务///////////////////////////////////////////////////////
void SyncDisk()
{
    std::cout << "刷新数据到磁盘" << std::endl;
    sleep(1);
}

void Download()
{
    std::cout << "下载数据到系统中" << std::endl;
    sleep(1);
}

void PrintLog()
{
    std::cout << "打印日志到本地" << std::endl;
    sleep(1);
}

void UpdateStatus()
{
    std::cout << "更新用户的状态" << std::endl;
    sleep(1);
}

typedef void (*task_k)();//函数指针类型
task_k tasks[4] = {SyncDisk,Download,PrintLog,UpdateStatus};//任务表
/////////////////////////////////////////////////进程池///////////////////////////////////////////////////////

enum
{
    OK = 1,
    PIPE_ERROR,
    FORK_ERROR
};

int gprocessnum = 5;

// typedef std::function<void (int)> task_t;//等价于
using cb_t = std::function<void (int)>;

void DoTask(int pipefd)//子进程完成任务:子进程视角
{
    while (true)
    {
        int task_code = 0;
        ssize_t n = read(pipefd,&task_code,sizeof(task_code));//以4字节的方式读数据
        if(n == sizeof(task_code))//因为我们往管道里面写的也是4字节为单位的数据
        {
            if(task_code >= 0 && task_code < 4)
            {
                tasks[task_code]();//执行任务标中的任务
            }
        }
        else if(n == 0)
        {
            //父进程不写数据了,要结束了,所以这个子进程也要结束了
            std::cout << "task quit" << std::endl;
            break;
        }
        else
        {
            //读取数据错误:n < 0 || n > sizeof(task_code)
            perror("read");
            break;
        }

    }
}



class ProcessPool
{
private:
    class Channel // 父进程管理"管道"
    {
    public:
        Channel(int wfd, pid_t pid)
            : _wfd(wfd), _sub_pid(pid)
        {
            _sub_name = "sub-channel-" + std::to_string(_sub_pid);
        }

        void PrintInfo()
        {
            printf("wfd:%d,who:%d,channel name:%s\n", _wfd, _sub_pid, _sub_name.c_str());
        }
        ~Channel()
        {

        }
        void Write(int index)
        {
            ssize_t n = write(_wfd,&index,sizeof(index));//约定以4字节写,子进程以4字节子进程读
            (void)n;
        }

        std::string Name()
        {
            return _sub_name;
        }

        void ClosePipe()
        {
            close(_wfd);
        }

        void Wait() 
        {
            pid_t rid = waitpid(_sub_pid,nullptr,0);
            (void)rid;
        }

    private:
        int _wfd;
        pid_t _sub_pid;
        std::string _sub_name;
    };
public:
    ProcessPool()
    {
        srand((unsigned int)time(nullptr) ^ getpid());//种下种子,方便后面选择任务
    }
    ~ProcessPool()
    {
    }

    void Quit()
    {
        //1.关闭所有的管道 = 导致通信失效 = OS 杀掉所有的子进程
        for(auto& c : channels)
        {
            c.ClosePipe();
            //c.Wait();//为什么不能边关闭管道,边回收子进程? 答:创建多个子进程导致父进程的文件描述表被多个子进程的继承,进而导致管道的指向有多个,或者说前面创建的管道的写端
            //有多个子进程指向,所以关闭第一个父进程的指向的第一个管道的写端,也没有用,因为后面的子进程也指向这个写端
            //解决方案:1.从最后一个管道的开始关闭写端,进而创建减少前面的写端被子进程的指向
        }
        //2.回收所有子进程
        for(auto& c : channels)
        {
            c.Wait();
        }

        //方案1:
        // int end = channels.size() - 1;
        // while(end >= 0)
        // {
        //     channels[end].ClosePipe();
        //     channels[end].Wait();
        //     end--;
        // }

        //方案2:也可以边关闭管道边回收子进程,只要在创建子进程的时候,关闭继承父进程的所以指向的管道的写端的就行
        //代码在创建子进程 id == 0 时,留个痕迹

    }

    void Init(cb_t cb)
    {
        CreatProcessChannel(cb);
    }

    void Debug()
    {
        for (auto &e : channels)
        {
            e.PrintInfo();
        }
    }

    void Run()
    {
        int cnt = 5;
        while (cnt--) // 不断循环选择:channel(管道)= 子进程 来完成任务
        {
            std::cout << "------------------------------------------------------------" << std::endl;
            int who = SelectChannel(); // 选择一个子进程就是选择一个下标
            std::cout << "who:" << who << std::endl;
            int itask = SelectTask();// 选择一个任务给子进程完成
            std::cout << "itask:" << itask << std::endl;
            //把任务发送给管道,让管道对应的子进程完成对应的任务
            printf("发送 %d to %s\n",itask,channels[who].Name().c_str());
            SendTask2Salver(itask,who);
            sleep(1);//一秒完成一个任务
        }
    }

private:


    void SendTask2Salver(int itask,int who)
    {
        if(itask >= 4 || itask < 0)
            return;
        if(who < 0 || who >= channels.size())
            return;
        
        channels[who].Write(itask);
    }

    int SelectChannel()
    {
        static int index = 0;
        int selected = index;
        index++;
        index %= channels.size();
        
        return selected;
    }

    int SelectTask()
    {
        //随机选择任务
       int itask = rand() % 4;//选择随机任务下标
       return itask; 
    }
    void CreatProcessChannel(cb_t cb)
    {

        for (int i = 0; i < gprocessnum; i++)
        {
            int pipefd[2] = {0}; // 创建多个管道和多个进程
            int n = pipe(pipefd);
            if (n < 0)
            {
                std::cerr << "pipe create error" << std::endl;
                exit(PIPE_ERROR);
            }
            pid_t id = fork();
            if (id < 0)
            {
                std::cerr << "fork erroe" << std::endl;
                exit(FORK_ERROR);
            }
            else if (id == 0)
            {
                //痕迹:
                // //关闭子进程的历史的写端
                // if(!channels.empty())
                // {
                //     //代表之前创建过子进程,我们只需要借用上一个 channel 对象来在这个进程关闭写端点的指向就行
                //     //关闭上一个的 channel 的 wfd 不会影响上一个 chanel 的 wfd ,因为当前在创建一个新的子进程,只会影响当前的子进程
                //     for(auto& e : channels)
                //     {
                //         e.ClosePipe();
                //     }
                // }


                // child
                close(pipefd[1]); // read
                // DoTask(pipefd[0]);
                cb(pipefd[0]);//更好的写法,使用回调函数来让子进程完成任务
                exit(OK); // 意味着子进程执行完任务之后,就不会执行下面的代码,也就是说执行整个循环的至始至终只有父进程
            }
            else
            {
            }
            // parent
            close(pipefd[0]);
            // write
            // Channel ch(pipefd[1], id); // 保存子进程 id 和对应的 写端管道,防止丢失
            // channels.push_back(ch);
            //更好的方法
            channels.emplace_back(pipefd[1],id);
            std::cout << "创建子进程:" << id << "成功" << std::endl;
            sleep(1); // 每隔一秒创建一个子进程
        }
    }

    std::vector<Channel> channels; // 管理使用的 Channel 容器
};

int main()
{
    ProcessPool pp;//创建进程池对象
    pp.Init(DoTask);//创建多进程
    // 父进程控制子进程
    pp.Run();
    pp.Debug();
    //释放和回收所以资源(管道、子进程)
    pp.Quit();    

    return 0;
}
相关推荐
p***323534 分钟前
Nginx 配置前端后端服务
运维·前端·nginx
王火火(DDoS CC防护)35 分钟前
服务器隐藏源IP要如何操作呢?
服务器·ddos攻击
草莓熊Lotso35 分钟前
Git 多人协作全流程实战:分支协同 + 冲突解决 + 跨分支协助
linux·运维·服务器·人工智能·经验分享·git·python
盛码笔记35 分钟前
部署Django+React项目到服务器
服务器·react.js·django
编程小Y38 分钟前
Nginx 反向代理
运维·nginx
小时候的阳光8 小时前
Docker版Percona Xtrabackup全量压缩脚本
运维·docker·容器
生信大表哥9 小时前
单细胞测序分析(五)降维聚类&数据整合
linux·python·聚类·数信院生信服务器
“αβ”9 小时前
MySQL表的操作
linux·网络·数据库·c++·网络协议·mysql·https
十五年专注C++开发9 小时前
Asio2: 一个基于 Boost.Asio 封装的高性能网络编程库
网络·c++·boost·asio·asio2