通过父进程 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;
}