**摘要:**本文通过C++实现一个基于管道通信的进程池,详细讲解了从准备工作、创建信道、初始化子进程,到父进程派发任务、等待子进程退出、处理bug以及最终代码汇总的完整流程。文章重点剖析了进程池实现中常见的文件描述符继承问题,并给出了正确的解决方案,适合学习Linux进程通信与多进程编程的读者参考。
目录
1.准备工作
C++的后缀除了.cpp/.cc外还可以有.cxx,我们之前写C语言的时候一般都采用头源分离的做法,即:头文件在.h文件里,实现的源代码在.c/.cpp/.cc里,这样做是比较推荐的做法之一,同样,我们也可以把头文件源文件写在一个文件里,这样的文件以.hpp结尾,有些开源的代码就放在.hpp文件里,后期学习的时候有可能要写几十个文件,因此这样的方式是比较好的,可以减少文件量!
我们创建main.cc和process.hpp以及Makefile有:

然后我们在Makefile写下如下代码:
bash
process_pool:main.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f process_pool
在process.hpp写下如下代码:
cpp
//防止头文件被重复包含
#pragma
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<unistd.h>
#include<string>
#include<vector>
#include<sys/wait.h>
#include<sys/types.h>
//创建的进程数量
const int gdefault_process_num = 5;
在main.cc里写下如下代码:
cpp
#include "process.hpp"
int main()
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
// 2.我们要创建一大堆进程
pid_t id = fork();
if (id == 0)
{
// 子进程
// 关闭写
close(pipefd[1]);
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
// 信道建立好了
}
return 0;
}
但是这里出现了一个问题,父进程怎么知道是往哪个管道传输信息?
2.创建信道
我们要描述信道的类:
cpp
//描述信道的类
class Channel
{
public:
//无参的构造函数
Channel(){}
//有参的构造函数
Channel(int fd,const std::string& name,pid_t id)
:_wfd(fd),
_name(name),
_sub_target(id)
{}
//析构函数
~Channel(){}
private:
int _wfd;//写端描述符值
std::string _name;//通道的名字
pid_t _sub_target;//目标子进程
};
主函数改为:
cpp
int main()
{
std::vector<Channel> channels;//表示未来所有的信道
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
// 2.我们要创建一大堆进程
pid_t id = fork();
if (id == 0)
{
// 子进程
// 关闭写
close(pipefd[1]);
//子进程要做的事情
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
//通道名字
std::string name = "channel-" + std::to_string(i);
//第一种方式:
//构造类
//Channel channel(pipefd[1],name,id);
//把新的信道导入
//channels.push_back(channel);
//第二种方式:
//使用emplace_back直接导入
channels.emplace_back(pipefd[1],name,id);
}
//父进程:对子进程控制,就会转化为对channels的控制
return 0;
}
各位可以自己按照这样的思路去写,然后测试一下防止出现错误,也可以自己去判断一下管道和创建进程是否失败!
不过这样全写到main函数不太好,因此我们可以封装成一个函数,改为:
cpp
//初始化进程池
bool InitProcessPool(std::vector<Channel> &channels)
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0) return false;
// 2.我们要创建一大堆进程
pid_t id = fork();
if(id < 0) return false;
if (id == 0)
{
// 子进程
// 关闭写
close(pipefd[1]);
//子进程要做的事情
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
//通道名字
std::string name = "channel-" + std::to_string(i);
channels.emplace_back(pipefd[1],name,id);
}
return true;
}
int main()
{
std::vector<Channel> channels;//表示未来所有的信道
//父进程:对子进程控制,就会转化为对channels的控制
InitProcessPool(channels);
return 0;
}
但是我们在初始化的时候子进程在干什么呢?
3.初始化子进程
我们包含一个头文件functional:
cpp
#include<functional>
然后写下:
cpp
using callback_t = std::function<void (int fd)>;
这个代码等同于:
cpp
typedef std::function<void (int fd)> callback_t;
因此,我们把初始化进程池加个参数,类型为callback_t,这个相当于回调函数,代码改为:
cpp
//初始化进程池
bool InitProcessPool(std::vector<Channel> &channels,callback_t cb)
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0) return false;
// 2.我们要创建一大堆进程
pid_t id = fork();
if(id < 0) return false;
if (id == 0)
{
// 子进程
// 关闭写
close(pipefd[1]);
//子进程要做的事情(从管道读)
cb(pipefd[0]);
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
//通道名字
std::string name = "channel-" + std::to_string(i);
channels.emplace_back(pipefd[1],name,id);
}
return true;
}
然后我们在main函数传第二个参数的时候要传入一个函数,可以写为一个lambda表达式,即:
cpp
int main()
{
std::vector<Channel> channels;//表示未来所有的信道
//父进程:对子进程控制,就会转化为对channels的控制
//想让子进程阻塞住
InitProcessPool(channels,[](int fd){
while(true)
{
int code = 0;
std::cout << "子进程阻塞" << getpid() << std::endl;
ssize_t n = read(fd,&code,sizeof(code));
if(n > 0)
std::cout << "子进程被唤醒" << getpid() << std::endl;
}
});
return 0;
}
这个代码写完后不要去运行,因为你运行后就会疯狂打印子进程阻塞:

因为我们最后父进程并没有等待子进程结束,所以会直接退出,此时的五个子进程就变成孤儿进程了,Ctrl+C也没法终止,只能再开一台机器依次杀掉这五个进程!
4.父进程给指定子进程派发任务
首先我们需要选择一个子进程,后面才能让这个子进程执行任务!我们最好均衡的选择信道,保证每个进程都有任务可做,不均衡会导致有些进程停不下来,有些进程非常闲,可以采用随机数或者轮换执行的策略或者在Channel定义一个load每次找最小的load,让load最小的进程去做这件事(这个方式可以自己去试一下)。
我们需要先给类加几个方法,方便外界获取成员变量:
cpp
// 描述信道的类
class Channel
{
public:
// 无参的构造函数
Channel() {}
// 有参的构造函数
Channel(int fd, const std::string &name, pid_t id)
: _wfd(fd),
_name(name),
_sub_target(id)
{}
// 析构函数
~Channel() {}
//得到fd
int Fd() {return _wfd;}
std::string Name() {return _name;}
private:
int _wfd; // 写端描述符值
std::string _name; // 通道的名字
pid_t _sub_target; // 目标子进程
};
然后在main加:
cpp
// 2.唤醒指定的子进程,让该子进程完成指定任务!
// 2.1选择一个子进程(选择一个信道)
int index = 0;
// 采用轮询做法
while (true)
{
int who = index;
index++;
//更新index
index %= channels.size();
int x = 1;
std::cout << "选择信道:" << channels[who].Name() << std::endl;
//往信道里写1
write(channels[who].Fd(),&x,sizeof(x));
sleep(1);
}
我们现在可以调整代码了,把刚刚在main.cc代码全部拷贝到process.hpp里,并封装为一个进程池的类:
cpp
//进程池
class ProcessPool
{
public:
// 构造函数
ProcessPool(int num = gdefault_process_num)
: _num(num) {}
// 析构函数
~ProcessPool() {}
// 初始化进程池
bool InitProcessPool(callback_t cb)
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
return false;
// 2.我们要创建一大堆进程
pid_t id = fork();
if (id < 0)
return false;
if (id == 0)
{
// 子进程
// 关闭写
close(pipefd[1]);
// 子进程要做的事情(从管道读)
// 回调函数
cb(pipefd[0]);
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
// 通道名字
std::string name = "channel-" + std::to_string(i);
_channels.emplace_back(pipefd[1], name, id);
}
return true;
}
// 2.唤醒指定的子进程,让该子进程完成指定任务!
// 2.1选择一个子进程(选择一个信道)
void CtrlSubSuccess(int count)
{
if(count < 0) return;
int index = 0;
// 采用轮询做法
while (count--)
{
int who = index;
index++;
// 更新index
index %= _channels.size();
int x = 1;
std::cout << "选择信道:" << _channels[who].Name() << std::endl;
// 往信道里写1
write(_channels[who].Fd(), &x, sizeof(x));
sleep(1);
}
}
private:
std::vector<Channel> _channels; // 表示未来所有的信道
int _num; // 子进程的数量
};
然后把main函数改为:
cpp
int main()
{
//构建进程池
ProcessPool pp;
// 1.创建进程池,让子进程进入自己的工作流中,父进程得到channels
// 父进程:对子进程控制,就会转化为对channels的控制
// 想让子进程阻塞住
pp.InitProcessPool([](int fd) {
while(true)
{
int code = 0;
std::cout << "子进程阻塞" << getpid() << std::endl;
ssize_t n = read(fd,&code,sizeof(code));
if(n > 0)
std::cout << "子进程被唤醒" << getpid() << std::endl;
} });
//2.控制子进程
//要完成的任务数
int count = 10;
pp.CtrlSubSuccess(count);
return 0;
}
但是我们的父进程在自己结束后子进程并不会退出,需要我们自己一个一个杀掉这些进程,所以需要父进程等待所有子进程退出!
5.父进程等待子进程退出
这个方法应当封装成进程池的成员函数,如何让子进程退出呢?
我们知道,如果父进程(写端)关闭了,读端会返回0,因此我们可以给子进程的任务加个判断,只要返回为0就退出!代码改为:
cpp
#include "process.hpp"
int main()
{
//1.创建进程池,让子进程进入自己的工作流中,父进程得到channels
ProcessPool pp(5);
//2.初始化进程池
// 父进程:对子进程控制,就会转化为对channels的控制
// 想让子进程阻塞住
pp.InitProcessPool([](int fd) {
while(true)
{
int code = 0;
std::cout << "子进程阻塞" << getpid() << std::endl;
ssize_t n = read(fd,&code,sizeof(code));
if(n > 0)
std::cout << "子进程被唤醒" << getpid() << std::endl;
else if(n == 0)
{
std::cout << "子进程应该退出" << getpid() << std::endl;
break;
}
} });
//3.控制子进程
//要完成的任务数
int count = 10;
pp.CtrlSubSuccess(count);
//4.回收进程池
pp.WaitSubProcess();
std::cout << "父进程控制子进程完成,父进程结束" << std::endl;
return 0;
}
然后我们给描述信道的类加几个成员函数:
cpp
void Close() {close(_wfd);}
pid_t Target() {return _sub_target;}
void Wait()
{
//等待子进程退出
pid_t rid = waitpid(_sub_target,nullptr,0);
(void)rid;
}
然后在线程池的类加上如下代码:
cpp
//子进程随父进程退出
void WaitSubProcess()
{
//1.让所有子进程退出,直接关闭所有的channel即可
for(auto& c : _channels)
{
c.Close();
}
//2.回收所有子进程的退出状态
for(auto& c : _channels)
{
//等待子进程即回收子进程
c.Wait();
std::cout << "回收子进程: " << c.Target() << std::endl;
}
}
此时就能完成子进程退出了!
但是现在有几个问题:
①我们想让子进程干什么事情啊?
我们可以创建一个Task.hpp文件,里面弄几个任务,如:4个任务,用数组把他们封装起来,然后使用特定的任务码就能完成特定的任务了(用数组下标来完成指定任务)
6.让子进程完成特定的任务
我们需要创建Task.hpp文件写下如下代码:
cpp
#pragma
#include<iostream>
#include<string>
//函数指针
using task_t = std::function<void()>;
void Download()
{
std::cout << "我是一个Download任务" << std::endl;
}
void MySql()
{
std::cout << "我是一个MySql任务" << std::endl;
}
void Sync()
{
std::cout << "我是一个数据刷新同步任务" << std::endl;
}
void Log()
{
std::cout << "我是一个日志保存任务" << std::endl;
}
//存放各种函数的地址
std::vector<task_t> tasks;
//把所有的任务放到数组里面
class Init
{
public:
Init()
{
tasks.push_back(Download);
tasks.push_back(MySql);
tasks.push_back(Sync);
tasks.push_back(Log);
}
};
//实例化一个对象
Init ginit;
不过这样写确实有点麻烦了,各位可以试着把处理这些任务放到主函数去执行,但是建议是这样的!
之前我们写的是给每个进程都写入管道为1,我们现在要改一下,直接把int x = 1改为随机数,随机数种子放在进程池的构造函数里,即:
cpp
// 构造函数
ProcessPool(int num = gdefault_process_num)
: _num(num)
{
//生成随机数
srand(time(nullptr)^getpid()^0x777);
}
至于为什么这样写,因为进程的pid是随机的,时间是随机的,只要^一个比较大的数,随机数非常随机!
然后再把指定的进程去完成任务代码改为:
cpp
// 2.唤醒指定的子进程,让该子进程完成指定任务!
// 2.1选择一个子进程(选择一个信道)
void CtrlSubSuccess(int count)
{
if(count < 0) return;
int index = 0;
// 采用轮询做法
while (count--)
{
//1.选择一个进程
int who = index;
index++;
// 更新index
index %= _channels.size();
//2.选择一个任务(随机)
int x = rand() % tasks.size(); //[0,3]
//3.把任务推送给子进程
std::cout << "选择信道:" << _channels[who].Name() << std::endl;
// 往信道里四字节
write(_channels[who].Fd(), &x, sizeof(x));
sleep(1);
}
}
最后我们需要在main.cc里的Lambda表达式里函数实现改一下,之前是让父进程唤醒子进程即可,现在需要改为当读取到read返回值大于0即为sizeof(code)就要执行指定的任务,具体实现如下:
cpp
int main()
{
//1.创建进程池,让子进程进入自己的工作流中,父进程得到channels
ProcessPool pp(5);
//2.初始化进程池
// 父进程:对子进程控制,就会转化为对channels的控制
// 想让子进程阻塞住
pp.InitProcessPool([](int fd) {
while(true)
{
int code = 0;
std::cout << "子进程阻塞" << getpid() << std::endl;
ssize_t n = read(fd,&code,sizeof(code));
//if(n > 0)
if(n == sizeof(code)) //写的时候是按照四字符写的,读也一样
{
//code:任务码
std::cout << "子进程被唤醒" << getpid() << std::endl;
if(code >= 0 && code < tasks.size())
{
//完成指定的任务
tasks[code]();
}
else
{
//实际上是肯定不会执行该代码
std::cerr << "父进程给我的任务码是不对的" << code << std::endl;
}
}
else if(n == 0)
{
std::cout << "子进程应该退出" << getpid() << std::endl;
break;
}
else
{
//证明读取失败
std::cerr << "read fd: " << fd << ",error" << std::endl;
}
} });
//3.控制子进程
//要完成的任务数
int count = 10;
pp.CtrlSubSuccess(count);
//4.回收进程池
pp.WaitSubProcess();
std::cout << "父进程控制子进程完成,父进程结束" << std::endl;
return 0;
}
7.处理bug
实际上我们的代码是有一个bug的,我们想让父进程等待所有子进程结束,我们是这样写的:
cpp
//子进程随父进程退出
void WaitSubProcess()
{
//1.让所有子进程退出,直接关闭所有的channel即可
for(auto& c : _channels)
{
c.Close();
}
//2.回收所有子进程的退出状态
for(auto& c : _channels)
{
//等待子进程即回收子进程
c.Wait();
std::cout << "回收子进程: " << c.Target() << std::endl;
}
}
但是不应该这样写吗?
cpp
//子进程随父进程退出
void WaitSubProcess()
{
//让所有子进程退出,直接关闭所有的channel即可
for(auto& c : _channels)
{
c.Close();
c.Wait();
}
}
不应该是关闭子进程之后就直接进入等待,为什么要分开?
我们试一下可不可以:

我们发现前面是很正常的,但是最后就会一直卡在这里,不会继续往下运行了!
为什么?
因为我们代码里有一个bug,这是初始化进程池的代码:

父进程有自己的文件描述符表,当创建一个管道时,那么就会创建打开文件描述符为3和4的文件,因为父进程只写,因此,3号文件(只读)会被关闭,而4号文件并不会被关闭,由于文件描述符的分配规则是,只要有最小的没有被使用的那个文件描述符就会分配给你,因此,当创建第二个管道时,真实情况是,它的读文件是3号文件,而写文件是5号文件,随后读文件(3)被关闭,等待下次被使用......
那么子进程会继承父进程的文件描述符表,第一个子进程会继承文件描述符为1-4的文件描述符表,第二个子进程要继承被打开的文件,那么4也被打开了,5也被打开了,那么4这个文件会不会被继承下来呢?
会的,子进程最终不光继承了父进程的除自己的写端之外的文件,还继承了上一个子进程的写端文件(4号文件),相当于说,每个子进程都有自己的兄弟进程+1(自己的)写端文件。
因此,如果我们创建了5个子进程,那么第一个文件就有5个写端(后面的每个子进程都会继承它的写端,还有一个父进程也指向这个写端),同理,第二个文件有4个写端(有四个进程指向它的写端),只有最后一个子进程只有一个子端,但是它也指向它所有的哥哥。
因此,我们如果这样写:

执行代码后,父进程会关闭第一个子进程的写端,然后就在waitpid阻塞等待子进程退出了,我们之前说过,如果想要把一个子进程退出,需要所有指向该进程的写端全部关闭,而现在是5个进程都指向第一个子进程的写端,如果父进程关闭了,那么还是有4个进程指向该进程的写端,因此,这个子进程并不会退出,所以就会一直阻塞住!
所以为了解决这个bug,有以下方式:
①倒着关闭子进程的写端,因为最后一个子进程的写端只有父进程指向的,所以就可以等父进程关闭自己的写端之后,该子进程就能正确退出了:
cpp
//子进程随父进程退出
void WaitSubProcess()
{
//这样会一直阻塞住
//1.倒着关闭
for(int end = _channels.size()-1; end >= 0;end--)
{
_channels[end].Close();
_channels[end].Wait();
}
}
但是我们不要这样倒着去关闭,因为还是有bug,因此最好还是用之前的方式!
②
cpp
//子进程随父进程退出
void WaitSubProcess()
{
// //1.倒着关闭
// for(int end = _channels.size()-1; end >= 0;end--)
// {
// _channels[end].Close();
// _channels[end].Wait();
// }
//2.分开关闭和等待的逻辑
//1.让所有子进程退出,直接关闭所有的channel即可
for(auto& c : _channels)
{
c.Close();
}
//2.回收所有子进程的退出状态
for(auto& c : _channels)
{
//等待子进程即回收子进程
c.Wait();
std::cout << "回收子进程: " << c.Target() << std::endl;
}
}
但是本来就有bug,应当以修bug为主,而不是让他一直存在
8.更改初始化进程池逻辑
我们在关闭子进程的文件描述符时,不仅仅要关闭自己文件描述符的写端,还需要关闭从父进程继承下来的被打开的文件描述符!
那么子进程要关闭的从父进程继承下来的fd在哪里?
就在_channels里,因为_channels本身会被继承下去的,且对父子来说是共享的,一旦_channels想要插入数据,那么操作系统就会在底层为_channels进行写时拷贝,因此,子进程不要担心父进程影响自己的_channels,此外fork之后,子进程只会看到所有历史进程的wfd,并不会受后续emplace_back的影响!
因此,我们只需要在子进程关闭的时候循环关闭_channels的_wfd即可:
cpp
// 初始化进程池
bool InitProcessPool(callback_t cb)
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
return false;
// 2.我们要创建一大堆进程
pid_t id = fork();
if (id < 0)
return false;
if (id == 0)
{
// 子进程
// 关闭写
//除了要关闭自己的写端还要关闭从父进程继承下来的写端
//这样写有bug
//close(pipefd[1]);
//应当这样写
for(auto& c : _channels)
{
c.Close();
}
//但是还需要关闭自己的写端
close(pipefd[1]);
// 子进程要做的事情(从管道读)
// 回调函数
cb(pipefd[0]);
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
// 通道名字
std::string name = "channel-" + std::to_string(i);
// 第一种方式:构造类
// Channel channel(pipefd[1],name,id);
// 把新的信道导入
// channels.push_back(channel);
// 第二种方式:使用emplace_back直接导入
_channels.emplace_back(pipefd[1], name, id);
}
return true;
}
只需要添加一个循环即可关闭前面的所有子进程的写端了,此时就算我们等待时关闭也能成功:

9.代码汇总
可以去我的Gitee查看:
processpool · 谭旦0000/test_for_csdn - 码云 - 开源中国
也可以查看下面代码:
Makefile:
bash
testpipe:testpipe.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f testpipe
process.hpp:
cpp
//防止头文件被重复包含
#pragma
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<unistd.h>
#include<string>
#include<vector>
#include<sys/wait.h>
#include<sys/types.h>
#include<functional>
#include<memory>
#include<ctime>
#include "Task.hpp"
//typedef std::function<void (int fd)> callback_t;
using callback_t = std::function<void (int fd)>;
//创建的进程数量
const int gdefault_process_num = 5;
// 描述信道的类
class Channel
{
public:
// 无参的构造函数
Channel() {}
// 有参的构造函数
Channel(int fd, const std::string &name, pid_t id)
: _wfd(fd),
_name(name),
_sub_target(id)
{}
// 析构函数
~Channel() {}
// 得到fd
int Fd() { return _wfd; }
std::string Name() { return _name; }
void Close() {close(_wfd);}
pid_t Target() {return _sub_target;}
void Wait()
{
//等待子进程退出
pid_t rid = waitpid(_sub_target,nullptr,0);
(void)rid;
}
private:
int _wfd; // 写端描述符值
std::string _name; // 通道的名字
pid_t _sub_target; // 目标子进程
};
//进程池
class ProcessPool
{
public:
// 构造函数
ProcessPool(int num = gdefault_process_num)
: _num(num)
{
//生成随机数
srand(time(nullptr)^getpid()^0x777);
}
// 析构函数
~ProcessPool() {}
// 初始化进程池
bool InitProcessPool(callback_t cb)
{
for (int i = 0; i < gdefault_process_num; i++)
{
// 1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
return false;
// 2.我们要创建一大堆进程
pid_t id = fork();
if (id < 0)
return false;
if (id == 0)
{
// 子进程
// 关闭写
//除了要关闭自己的写端还要关闭从父进程继承下来的写端
//这样写有bug
//close(pipefd[1]);
//应当这样写
for(auto& c : _channels)
{
c.Close();
}
//但是还需要关闭自己的写端
close(pipefd[1]);
// 子进程要做的事情(从管道读)
// 回调函数
cb(pipefd[0]);
exit(0);
}
// 后面都是父进程
// 关闭读
close(pipefd[0]);
// 通道名字
std::string name = "channel-" + std::to_string(i);
// 第一种方式:构造类
// Channel channel(pipefd[1],name,id);
// 把新的信道导入
// channels.push_back(channel);
// 第二种方式:使用emplace_back直接导入
_channels.emplace_back(pipefd[1], name, id);
}
return true;
}
// 2.唤醒指定的子进程,让该子进程完成指定任务!
// 2.1选择一个子进程(选择一个信道)
void CtrlSubSuccess(int count)
{
if(count < 0) return;
int index = 0;
// 采用轮询做法
while (count--)
{
//1.选择一个进程
int who = index;
index++;
// 更新index
index %= _channels.size();
//2.选择一个任务(随机)
int x = rand() % tasks.size(); //[0,3]
//3.把任务推送给子进程
std::cout << "选择信道:" << _channels[who].Name() << std::endl;
// 往信道里四字节
write(_channels[who].Fd(), &x, sizeof(x));
sleep(1);
}
}
//子进程随父进程退出
void WaitSubProcess()
{
// //1.倒着关闭
// for(int end = _channels.size()-1; end >= 0;end--)
// {
// _channels[end].Close();
// _channels[end].Wait();
// }
//2.分开关闭和等待的逻辑
//1.让所有子进程退出,直接关闭所有的channel即可
for(auto& c : _channels)
{
c.Close();
c.Wait();
}
// //2.回收所有子进程的退出状态
// for(auto& c : _channels)
// {
// //等待子进程即回收子进程
// c.Wait();
// std::cout << "回收子进程: " << c.Target() << std::endl;
// }
}
private:
std::vector<Channel> _channels; // 表示未来所有的信道
int _num; // 子进程的数量
};
cpp
#include "process.hpp"
int main()
{
//1.创建进程池,让子进程进入自己的工作流中,父进程得到channels
ProcessPool pp(gdefault_process_num);
//2.初始化进程池
// 父进程:对子进程控制,就会转化为对channels的控制
// 想让子进程阻塞住
pp.InitProcessPool([](int fd) {
while(true)
{
int code = 0;
std::cout << "子进程阻塞" << getpid() << std::endl;
ssize_t n = read(fd,&code,sizeof(code));
//if(n > 0)
if(n == sizeof(code)) //写的时候是按照四字符写的,读也一样
{
//code:任务码
std::cout << "子进程被唤醒" << getpid() << std::endl;
if(code >= 0 && code < tasks.size())
{
//完成指定的任务
tasks[code]();
}
else
{
//实际上是肯定不会执行该代码
std::cerr << "父进程给我的任务码是不对的" << code << std::endl;
}
}
else if(n == 0)
{
std::cout << "子进程应该退出" << getpid() << std::endl;
break;
}
else
{
//证明读取失败
std::cerr << "read fd: " << fd << ",error" << std::endl;
}
} });
//3.控制子进程
//要完成的任务数
int count = 10;
pp.CtrlSubSuccess(count);
//4.回收进程池
pp.WaitSubProcess();
std::cout << "父进程控制子进程完成,父进程结束" << std::endl;
return 0;
}
Task.hpp:
cpp
#pragma
#include<iostream>
#include<string>
//函数指针
using task_t = std::function<void()>;
void Download()
{
std::cout << "我是一个Download任务" << std::endl;
}
void MySql()
{
std::cout << "我是一个MySql任务" << std::endl;
}
void Sync()
{
std::cout << "我是一个数据刷新同步任务" << std::endl;
}
void Log()
{
std::cout << "我是一个日志保存任务" << std::endl;
}
//存放各种函数的地址
std::vector<task_t> tasks;//任务表
//把所有的任务放到数组里面
class Init
{
public:
Init()
{
tasks.push_back(Download);
tasks.push_back(MySql);
tasks.push_back(Sync);
tasks.push_back(Log);
}
};
//实例化一个对象
Init ginit;