Linux 进程池代码

Linux进程池实现代码解析

ProcessPool.cc

#include "Task.hpp"  // 包含任务加载相关声明(如LoadTask函数和task_t类型定义)
#include <string>    // 字符串处理
#include <vector>    // 动态数组容器,用于存储进程通道和任务列表
#include <cstdlib>   // 标准库函数(如rand、exit)
#include <ctime>     // 时间相关函数(用于随机数种子)
#include <cassert>   // 断言宏(用于调试阶段检查关键操作结果)
#include <unistd.h>  // 系统调用(如pipe、fork、dup2、close、read、write、sleep)
#include <sys/stat.h>
#include <sys/wait.h> // 进程等待函数(waitpid)

// 进程池规模:创建10个子进程
const int processnum = 10;
// 存储所有可执行任务的列表(task_t应为函数指针类型,定义在Task.hpp中)
std::vector<task_t> tasks;

// 通道类:封装父进程与单个子进程的通信信息
class channel
{
public:
    // 构造函数:初始化通道的通信fd、子进程PID、进程名称
    // 参数:cmdfd-父进程向子进程发命令的管道写端fd;slaverid-子进程PID;processname-子进程名称(日志用)
    channel(int cmdfd, int slaverid, const std::string &processname)
    :_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname)
    {}
public:
    int _cmdfd;               // 父进程发送任务的管道写端文件描述符
    pid_t _slaverid;          // 对应子进程的PID(用于进程管理和日志打印)
    std::string _processname; // 子进程名称(自定义标识,方便调试日志输出)
};

// 子进程工作函数:子进程的核心逻辑
void slaver()
{
    // 循环读取父进程发送的命令(子进程标准输入已重定向为管道读端)
    while(true)
    {
        int cmdcode = 0; // 存储读取到的命令码(对应tasks列表的索引)
        // 从标准输入(管道读端)读取命令码,若父进程未发送数据则阻塞等待
        int n = read(0, &cmdcode, sizeof(int)); 
        
        if(n == sizeof(int)) // 成功读取到完整的命令码
        {
            // 打印日志:子进程PID和收到的命令码
            std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;
            // 检查命令码合法性,合法则执行对应的任务(tasks[cmdcode]是函数调用)
            if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();
        }
        if(n == 0) break; // 读取到0字节(父进程关闭管道写端),退出循环(子进程终止)
    }
}

// 初始化进程池:创建指定数量的子进程,建立父子进程通信管道,初始化通道列表
// 参数:channels-输出参数,用于存储所有子进程的通道信息(父进程持有)
void InitProcessPool(std::vector<channel> *channels)
{
    // 存储历史管道写端fd:用于子进程关闭非自己对应的写端,避免文件描述符泄漏
    std::vector<int> oldfds;
    // 循环创建processnum个子进程
    for(int i = 0; i < processnum; i++)
    {
        int pipefd[2]; // 管道文件描述符数组:pipefd[0]读端,pipefd[1]写端
        int n = pipe(pipefd); // 创建匿名管道(父子进程通信)
        assert(!n); // 调试阶段断言管道创建成功(生产环境需替换为错误处理)
        (void)n; // 消除未使用变量n的编译警告

        pid_t id = fork(); // 创建子进程
        if(id == 0) // 子进程执行逻辑
        {
            // 打印子进程要关闭的历史写端fd(调试用)
            std::cout << "child: " << getpid() << " close history fd: ";
            for(auto fd : oldfds) {
                std::cout << fd << " ";
                close(fd); // 关闭历史管道写端(仅保留自己对应的读端)
            }
            std::cout << "\n";

            close(pipefd[1]); // 子进程不需要写端,关闭管道写端
            dup2(pipefd[0], 0); // 将标准输入(0)重定向为管道读端(pipefd[0])
            close(pipefd[0]); // 重定向后,原始pipefd[0]可关闭(避免fd泄漏)
            slaver(); // 子进程执行工作逻辑
            std::cout << "process : " << getpid() << " quit" << std::endl; // 退出日志
            exit(0); // 子进程执行完后退出(避免继续执行父进程逻辑)
        }
        // 父进程执行逻辑
        close(pipefd[0]); // 父进程不需要读端,关闭管道读端

        // 构造子进程通道信息,添加到通道列表
        std::string name = "process-" + std::to_string(i); // 子进程名称(process-0~process-9)
        channels->push_back(channel(pipefd[1], id, name));
        oldfds.push_back(pipefd[1]); // 记录当前管道写端,供后续子进程关闭

        sleep(1); // 延迟1秒创建下一个子进程(避免进程创建过快导致日志混乱)
    }
}

// 调试函数:打印所有子进程的通道信息(fd、PID、名称)
void Debug(const std::vector<channel> &channels)
{
    for(const auto &c :channels)
    {
        std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;
    }
}

// 菜单函数:打印用户可选的操作(对应不同任务)
void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

// 控制子进程:父进程读取用户输入,选择任务和子进程,发送任务命令
// 参数:channels-进程池通道列表(父进程通过该列表与子进程通信)
void ctrlSlaver(const std::vector<channel> &channels)
{
    int which = 0; // 记录当前要发送任务的子进程索引(轮询使用)
    while(true)
    {
        int select = 0;
        Menu(); // 打印菜单
        std::cout << "Please Enter@ ";
        std::cin >> select; // 读取用户选择

        if(select <= 0 || select >= 5) break; // 输入0或超出范围,退出控制循环

        // 将用户选择(1-4)转换为任务索引(0-3,对应tasks列表)
        int cmdcode = select - 1;

        // 打印日志:父进程发送的命令码、目标子进程PID和名称
        std::cout << "father say: " << " cmdcode: " <<
            cmdcode << " already sendto " << channels[which]._slaverid << " process name: " 
                << channels[which]._processname << std::endl;
        
        // 通过管道写端发送命令码给目标子进程
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));

        which++; // 轮询切换到下一个子进程
        which %= channels.size(); // 确保索引不越界(循环使用进程池)
    }
}

// 退出清理函数:关闭所有管道写端,等待所有子进程退出(避免僵尸进程)
// 参数:channels-进程池通道列表
void QuitProcess(const std::vector<channel> &channels)
{
    // 遍历所有通道:关闭管道写端(触发子进程read返回0,子进程退出),然后等待子进程回收
    for(const auto &c : channels){
        close(c._cmdfd); // 关闭父进程的管道写端
        waitpid(c._slaverid, nullptr, 0); // 等待对应子进程退出(阻塞)
    }

    // 以下是其他可选实现方式(注释保留供参考)
    // version1: 反向遍历关闭(效果与正向一致,无本质区别)
    // int last = channels.size()-1;
    // for(int i = last; i >= 0; i--)
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid, nullptr, 0);
    // }

    // version2: 先关闭所有写端,再统一等待(子进程会同时收到退出信号)
    // for(const auto &c : channels) close(c._cmdfd);
    // for(const auto &c : channels) waitpid(c._slaverid, nullptr, 0);
}

// 主函数:程序入口,串联初始化、控制、清理流程
int main()
{
    LoadTask(&tasks); // 加载任务列表(具体任务实现在Task.hpp中,将任务函数添加到tasks向量)

    // 设置随机数种子(结合时间、PID、固定值,避免多进程同时启动时种子重复)
    srand(time(nullptr)^getpid()^1023); 
    
    std::vector<channel> channels; // 存储进程池的所有通道信息
    InitProcessPool(&channels);    // 1. 初始化进程池(创建子进程+建立通信管道)
    // Debug(channels); // 可选:打印通道信息调试

    ctrlSlaver(channels);          // 2. 父进程控制子进程执行任务(用户交互)

    QuitProcess(channels);         // 3. 程序退出时清理资源(回收子进程)
    return 0;
}

Task.hpp

#pragma once

#include <iostream>
#include <vector>

typedef void (*task_t)();

void task1()
{
    std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{
    std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{
    std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{
    std::cout << "lol 用户释放技能,更新用的血量和蓝量" << 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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值