Linux---进程池

一、进程池的基本结构

MASTER Slaver(主从)模式的进程池

预先创建进程,形成进程池,需要任务时让进程执行任务,极大地提高了效应效率!

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

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

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

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

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

typedef void (*task_t)(); // 函数指针

task_t tasks[4] = {SyncDisk, Download, PrintLog, UpdateStatus}; // 任务表 ---面向对象化

///////////////////////////////进程池相关////////////////////////////////
enum
{
    OK = 0,
    PIPE_ERROR,
    FORK_ERROR
};

// 子进程的入口函数
void DoTask(int fd)
{
    while (true)
    {
        int task_code = 0;
        ssize_t n = read(fd, &task_code, sizeof(task_code)); // 问题1: 子进程需要sleep吗?不需要!
        if (n == sizeof(task_code))
        {
            if (task_code >= 0 && task_code < 4)
            {
                tasks[task_code](); // 执行任务表中的任务
            }
        }
        else if (n == 0)
        {
            // 父进程要结束,我也应该要退出了!
            std::cout << getpid() << ": task quit ..." << std::endl;
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }
}

const int gprocessnum = 5;
using cb_t = std::function<void(int)>;

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);
        }
        ~Channel()
        {
        }
        void Write(int index)
        {
            ssize_t n = write(_wfd, &index, sizeof(index)); // 约定的4字节发送吗?
            (void)n;
        }
        std::string Name()
        {
            return _sub_name;
        }
        void ClosePipe()
        {
            std::cout << "关闭wfd: " << _wfd << std::endl;
            close(_wfd);
        }
        void Wait()
        {
            pid_t rid = waitpid(_sub_pid, nullptr, 0);
            (void)rid;
            std::cout << "回收子进程: " << _sub_pid << std::endl;
        }
        void PrintInfo()
        {
            printf("wfd: %d, who: %d, channel name: %s\n", _wfd, _sub_pid, _sub_name.c_str());
        }

    private:
        int _wfd;              // 1. wfd
        pid_t _sub_pid;        // 2. 子进程是谁
        std::string _sub_name; // 3. 子channel的名字

        // int cnt;
    };

public:
    ProcessPool()
    {
        srand((unsigned int)time(nullptr) ^ getpid());
    }
    ~ProcessPool() {}
    void Init(cb_t cb)
    {
        CreateProcessChannel(cb);
    }
    void Run()
    {
        int cnt = 10;
        // while (cnt--)
        while (true)
        {
            // std::cout << "------------------------------------------------" << std::endl;
            // // 1. 选择一个任务
            // int itask = SelectTask();
            // std::cout << "itask: " << itask << std::endl;

            // // 2. 选择一个channel(管道+子进程),本质是选择一个下标数字
            // int index = SelectChannel();
            // std::cout << "index: " << index << std::endl;

            // // 3. 发送一个任务给指定的channel(管道+子进程)
            // printf("发送 %d to %s\n", itask, channels[index].Name().c_str());
            // SendTask2Salver(itask, index);
            sleep(1); // 一秒一个任务
        }
    }
    void Quit()
    {
        // version3:
        for (auto &channel : channels)
        {
            channel.ClosePipe();
            channel.Wait();
        }
        // version2: 逆向回收
        // int end = channels.size()-1;
        // while(end >= 0)
        // {
        //     channels[end].ClosePipe();
        //     channels[end].Wait();
        //     end--;
        // }

        // bug演示
        // for (auto &channel : channels) // 为什么不能这样写?应该怎么写?bug?
        // {
        //     channel.ClosePipe();
        //     channel.Wait();
        // }

        // version1
        // // 1. 让所有子进程退出
        // for (auto &channel : channels)
        // {
        //     channel.ClosePipe();
        // }

        // // 2. 回收子进程
        // for (auto &channel : channels)
        // {
        //     channel.Wait();
        // }
    }
    void Debug()
    {
        for (auto &c : channels)
        {
            c.PrintInfo();
        }
    }

private:
    void SendTask2Salver(int itask, int index)
    {
        if (itask >= 4 || itask < 0)
            return;
        if (index < 0 || index >= channels.size())
            return;

        channels[index].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 CreateProcessChannel(cb_t cb)
    {
        // 1. 创建多个管道和创建多个进程
        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 error" << std::endl;
                exit(FORK_ERROR);
            }
            else if (id == 0)
            {
                // 子进程关闭历史wfd, 影响的是自己的fd表
                if(!channels.empty())
                {
                    for(auto &channel : channels)
                        channel.ClosePipe();
                }

                // child
                close(pipefd[1]); // read
                cb(pipefd[0]);    // 回调: 让子进程调用出去,回调完成,他还会回来的!!!
                exit(OK);         // 根本不会执行后续代码,执行完自己的DoTask()函数之后,自己就退出了
            }
            else
            {
                // 父进程 write
                close(pipefd[0]);
                channels.emplace_back(pipefd[1], id);
                // Channel ch(pipefd[1], id);
                // channels.push_back(ch);
                std::cout << "创建子进程: " << id << " 成功..." << std::endl;
                sleep(1);
            }
        }
    }

private:
    // 0. 未来组织所有channel的容器
    std::vector<Channel> channels;
};

int main()
{
    // 1. 初始化进程池
    ProcessPool pp;
    pp.Init(DoTask);
    pp.Debug();

    // 2. 父进程控制子进程,channels
    pp.Run();

    // 3. 释放和回收所有资源(释放管道,回收子进程)
    pp.Quit();

    return 0;
}

子进程需要 sleep 吗?

完全不需要!

  • read(管道读端)阻塞系统调用

  • 没有任务时,子进程会自动休眠,不消耗 CPU

  • 有任务时自动唤醒执行

  • 手动 sleep 反而会降低效率

  • 父进程:管理者 ------ 创建子进程、创建管道、发任务、回收子进程

  • 子进程:打工人 ------ 从管道读任务编号、执行任务

  • 管道:通信工具 ------ 父写子读,一对一通信

  • 任务表:固定 4 个任务,用编号调用(0~3)

相关推荐
Shepherd06192 小时前
【IT 实战】Apache 反向代理 UniFi Controller 的终极指北(解决白屏、502、400 错误)
运维·网络·apache·it·unifi
嵌入小生0072 小时前
ARM --- 基础知识汇总
linux·嵌入式·arm·指令集
IMPYLH2 小时前
Linux 的 du 命令
linux·运维·服务器
祭曦念2 小时前
MySQL基础运维:mysqldump全量备份与恢复实操 | 新手可直接落地的备份指南
运维·数据库·mysql
!chen3 小时前
[Linux][虚拟串口]0x03一个特殊的字节
linux·运维·服务器
林姜泽樾3 小时前
Linux入门第十七章,systemctl软件启动和软连接
linux·运维·服务器·centos
CDN3603 小时前
360CDN 产品实测合集:CDN / 高防 / SDK 游戏盾真实反馈
运维·游戏·网络安全
济6173 小时前
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(自旋锁实现 LED 设备互斥访问)--- Ubuntu20.04自旋锁实验
linux·嵌入式·嵌入式linux驱动开发
i建模3 小时前
SSL: CERTIFICATE_VERIFY_FAILED feishu 机器人CoPaw
运维·网络·网络协议·ssl·openclaw