Linux:简易进程池编写

设计概念

进程池,即我们可以预先创建一堆子进程和对应的管道。等父进程有任务时派发给子进程工作。这样就可以节省开辟进程的花销:

当没有任务时,即管道为空。那么子进程就是阻塞状态不会影响其他进程工作效率。

但是注意我们要将任务均衡地派发给子进程,即实现负载均衡

Channel

首先我们要设计一个方案让父进程能统一管理与子进程之间的管道,最好的方法就是封装一个类。

那么类的成员变量必然要有管道的写端fd、子进程的pid还可以有给子进程的编号。

cpp 复制代码
class Channel
{
public:
    Channel(int wfd,pid_t id,const std::string&name)
        :_wfd(wfd),_subprocessid(id),_name(name)
        {}
    int GetWfd(){return _wfd;}
    pid_t GetPidProcessId(){return _subprocessid;}
    std::string GetName(){return _name;}
    //关闭写端
    void ColseChannel()
    {
        close(_wfd);
    }
    //等待子进程
    void Wait()
    {
        pid_t rid=waitpid(_subprocessid,nullptr,0);
        if(rid>0)
        {
            std::cout<<"wait"<<rid<<"success!"<<std::endl;
        }
    }
    ~Channel(){}
private:
    int _wfd;
    pid_t _subprocessid;
    std::string _name;
};

初始化

接下来我们要初始化Channel数组。

根据要创建的worker个数num,我们可以写一个简单的for循环创建管道和子进程。由子进程关闭写端,父进程关闭读端,即可。

但是我们要注意一个细节,这里以创建两个子进程举例,这里先创建第一个worker:

然后分别关闭读写端:

此时我们创建第二个worker:

然后分别关闭读写端:

这时我们发现第二个子进程对第一个管道的写端并没有关闭,此时有可能造成父进程最后无法结束进程。

因此我们在创建新的worker时,要将前面的worker的写端关闭:

cpp 复制代码
void CreatChannelAndSub(int num,std::vector<Channel>*channels,task_t task)
{
    for(int i=0;i<num;i++)
    {
        int pipefd[2]={0};
        int n=pipe(pipefd);
        //创建管道失败
        if(n<0)
            exit(1);
        //创建子进程
        pid_t id=fork();
        if(id==0)
        {
            //非空就要关闭前面的写端
            if(!channels->empty())
            {
                for(auto&channel:*channels)channel.ColseChannel();
            }
            close(pipefd[1]);
            //重定向到标准输入
            dup2(pipefd[0],0);
            //回调函数
            task();
            close(pipefd[0]);
            exit(0);
        }
        //父进程
        std::string channel_name="Channel-"+std::to_string(i);
        close(pipefd[0]);
        channels->emplace_back(pipefd[1],id,channel_name);
    }
}

创建任务

我们来实现不同的任务以分配给子进程,首先重命名下函数指针用以实现回调函数:

随意实现三个简单的任务:

cpp 复制代码
void Print()
{
    std::cout<<"I am print task"<<std::endl;
}
void DownLoad()
{
    std::cout<<"I am download task"<<std::endl;
}
void Flush()
{
    std::cout<<"I am flush task"<<std::endl;
}

创建回调表:

分配任务:

cpp 复制代码
int SelectTask()
{
    return rand()%TaskNum;
}

执行任务:

cpp 复制代码
void ExcuteTask(int number)
{
    if(number<0||number>2)
        return;
    tasks[number];
}

子进程工作

以及有了上面的一系列任务,我们是时候给子进程工作了:

cpp 复制代码
void work()
{
    while(true)
    {
        int command=0;
        int n=read(0,&command,sizeof(command));
        if(n==sizeof(int))
        {
            std::cout<<"pid is:"<<getpid()<<"handler task"<<std::endl;
            ExcuteTask(command);
        }
        //写端关闭
        if(n==0)
        {
            std::cout<<"sub process:"<<getpid()<<" quit"<<std::endl;
            break;
        }
    }
}

轮询方案

前面提到我们要实现负载均衡,这里可以简单实现为轮询。即轮流给子进程派送任务:

cpp 复制代码
int NextChannel(int channelnum)
{
    static int next=0;
    int channel=next;
    next++;
    next%=channelnum;
    return next;
}

分配工作

子进程的工作也有了,轮询方案也有了,就可以给子进程正是分配工作了:

cpp 复制代码
void SendTaskCommand(Channel &channel,int taskcommand)
{
    write(channel.GetWfd(),&taskcommand,sizeof(int));
}

void ctrlProcessOnce(std::vector<Channel>&channels)
{
    sleep(1);
    //挑选任务
    int taskcommand=SelectTask();
    //挑选信道和进程
    int channel_index=NextChannel(channels.size());
    //发送任务
    SendTaskCommand(channels[channel_index],taskcommand);
    std::cout<<std::endl;
    std::cout<<"taskcommand:"<<taskcommand<<"channel:"<<channels[channel_index].GetName()\
    <<"sub process:"<<channels[channel_index].GetPidProcessId()<<std::endl;
}

void ctrlProcess(std::vector<Channel>&channels,int times=-1)
{
    if(times>0)
    {
        while(times--)
            ctrlProcessOnce(channels);
    }
    else
    {
         while(true)
            ctrlProcessOnce(channels);
    }
}

关闭子进程和管道

有了前面关闭子进程所有写端的处理,我们能直接关闭子进程的读端进而使子进程退出,对子进程等待即可:

cpp 复制代码
void CleanUpChannel(std::vector<Channel>&channels)
{
    for(auto&channel:channels)
    {
        channel.ColseChannel();
        channel.Wait();
    }
}

Main

接下来就是在main函数里安排代码执行的顺序,我们还可以通过选项的形式控制生成的子进程数量:

cpp 复制代码
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        std::cerr<<"Usage:"<<argv[0]<<" processnum"<<std::endl;
    }
    int num=std::stoi(argv[1]);
    std::vector<Channel>channels;
    //1.创建子进程和信道
    CreatChannelAndSub(num,&channels,work);
    //2.控制子进程
    ctrlProcess(channels,5);
    //3.回收资源
    CleanUpChannel(channels);
    
    return 0;
}

来尝试运行代码:

完整代码

相关推荐
uxiang_blog2 小时前
如何使用U盘在Windows电脑上按装Fedora43 KDE?
linux·windows·重装系统·fedora43
奈何不吃鱼2 小时前
【安装配置教程】在linux部署java项目
java·linux·intellij-idea·jar
Ivy_belief2 小时前
Linux:设置和获取单片机gpio引脚和key值
linux·arm开发·qt·gpio·event事件
山南有清风2 小时前
开源对象存储项目一览
linux·分布式·对象存储·存储
学习CS的小白2 小时前
Linux系统(Ubuntu)安装Java 8
linux
AI大模型学徒2 小时前
Ubuntu_install(一)
linux·运维·ubuntu
soft20015252 小时前
Rocky Linux 9.6 环境下,Prometheus + Grafana 生产级安装手册
linux·grafana·prometheus
HalvmånEver2 小时前
Linux:基础IO(二)
linux·运维·服务器
sunshine~~~2 小时前
mac Ubuntu 下怎么安装中文语言环境 键盘一直切换不到中文
linux·ubuntu·macos·输入法