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);
}