前言
在学习了匿名管道的基本内容之后,你有没有想过自己使用一下匿名管道,本文就详细介绍一个匿名管道的实例–进程池。
匿名管道详解:进程间通信–匿名管道【Linux】
一、实例分析
本实例想通过管道实现一个功能 – 父进程控制一批子进程完成对应的任务。
简而言之,就是父进程通过管道给一批子进程分别发送任务代码,然后子进程完成对应的任务,可以分为以下几个步骤。
①加载任务列表
②创建子进程
③父进程派发任务
④回收资源
二、管道
什么是管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
原理
父进程指向一个管道文件,子进程继承父进程的指向关系,从而子进程也指向管道,进行通信。
三、实例代码
3.0 加载任务列表
由于我们需要指派子进程完成任务,首先需要一些任务列表,本实例不详细实现任务,只是打印输出执行任务成功,来模拟实现完成任务。
typedef void (*functor)();
一个数组指针,用来保存任务执行的函数。
vector<functor> functors;
一个方法集合,用vector
来存放一系列的任务执行的数组指针。
unordered_map<uint32_t, string> info;
存放uint32_t
类型的key值以及string
字符串,存放任务名称。
其中uint32_t
是unsighed int
的别名,为无符号整形。
生成任务
typedef void (*functor)();
vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;
void f1()
{
cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f2()
{
cout << "这是一个备份数据的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void f3()
{
cout << "这是一个处理网络链接的任务,执行的进程 ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl;
}
void loadFunctor()
{
info.insert({functors.size(), "处理日志的任务"});
functors.push_back(f1);
info.insert({functors.size(), "备份数据的任务"});
functors.push_back(f2);
info.insert({functors.size(), "处理网络链接的任务"});
functors.push_back(f3);
}
生成任务之后,在main
函数中使用loadFunctor
函数来加载任务列表。
3.1 创建子进程
创建五个子进程,并连接所对应的管道,父进程保存管道相关信息。
typedef pair<int32_t, int32_t> elem;
vector<elem> assignMap;
int32_t
: 进程pid,int32_t
:该进程对应的管道写端fd。
定义了一个名为 assignMap
的 vector,它的元素类型是 elem
,也就是一对 int32_t 类型的值。用来建立子进程和管道的连接。
// int32_t: 进程pid,int32_t:该进程对应的管道写端fd
typedef pair<int32_t, int32_t> elem;
int processNum = 5;
void createChildProcess(vector<elem> &assignMap)
{
// 创建procesNum个进程
for (int i = 0; i < processNum; i++)
{
// 定义保存管道fd的对象
int pipefd[2] = {0};
// 创建管道
pipe(pipefd);
// 创建子进程
pid_t id = fork();
if (id == 0)
{
// 子进程读取,r -> pipefd[0]
close(pipefd[1]);
// 子进程执行
work(pipefd[0]);
close(pipefd[0]);
exit(0);
}
// 父进程做的事情 -> pipefd[1]
close(pipefd[0]);
elem e(id, pipefd[1]);
assignMap.push_back(e);
}
}
子进程核心工作
子进程执行以下三个操作
- 阻塞等待管道中的任务信息
- 获取任务信息
- 处理任务信息
assert(s == sizeof(uint32_t));
(void)s;
assert
断言,是编译有效 (debug 模式),在release 模式下,断言就失效了。一旦断言失效,s
变量就是只被定义,没有被使用,release模式中,可能会有warning。
void work(int blockFd)
{
cout << "进程[" << getpid() << "]" << " 开始工作" << endl;
// 子进程核心工作的代码
while (true)
{
// a.阻塞等待 b.获取任务信息
uint32_t operatorCode = 0;
ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
if (s == 0) break;
assert(s == sizeof(uint32_t));
(void)s;
// c.处理任务
if (operatorCode < functors.size()) functors[operatorCode]();
}
cout << "进程[" << getpid() << "]" << " 结束工作" << endl;
}
3.2 父进程派发任务
父进程执行以下四个操作
- 随机选择一个子进程
- 随机选择一个任务
- 将任务分配给指定的子进程
- 打印任务相关信息
void blanceSendTask(const vector<elem> &processFds)
{
srand((long long)time(nullptr));
while(true)
{
sleep(1);
// 选择一个进程
uint32_t pick = rand() % processFds.size();
// 选择一个任务
uint32_t task = rand() % functors.size();
// 把任务给一个指定的进程
write(processFds[pick].second, &task, sizeof(task));
// 打印对应的提示信息
cout << "父进程指派任务->" << info[task]
<< "给进程:" << processFds[pick].first << " 编号:" << pick << endl;
}
}
3.3 回收资源
当子进程停止工作时,父进程等待子进程结束回收资源,并关闭管道。
void recycleResources(const vector<elem> &assignMap)
{
for (int i = 0; i < processNum; i++)
{
if (waitpid(assignMap[i].first, nullptr, 0) > 0)
cout << "wait for: pid = " << assignMap[i].first
<< " number: " << i << "wait sussess!" << endl;
close(assignMap[i].second);
}
}
3.4 主函数
int main()
{
vector<elem> assignMap;
// 加载任务列表
loadFunctor();
// 创建子进程
createChildProcess(assignMap);
cout << "create all process success!" << endl;
// 父进程,派发任务
blanceSendTask(assignMap);
// 回收资源
recycleResources(assignMap);
return 0;
}
输出样例
左图:监控脚本,监控进程信息
while :; do ps axj | head -1; ps axj | grep mypipe | grep -v grep; sleep 1;done
右图:代码执行信息
可以看到创建了父进程和五个子进程,子进程开始工作,并且父进程随机给五个子进程分配随机任务。
源代码
#include <iostream>
#include <unistd.h>
#include <vector>
#include <unordered_map>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
typedef void (*functor)();
vector<functor> functors; // 方法集合
// for debug
unordered_map<uint32_t, string> info;
void f1()
{
cout << "这是一个处理日志的任务,执行的进程ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}
void f2()
{
cout << "这是一个备份数据的任务,执行的进程ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}
void f3()
{
cout << "这是一个处理网络链接的任务,执行的进程ID [" << getpid() << "]"
<< ", 执行时间是 [" << time(nullptr) << "]" << endl << endl;
}
void loadFunctor()
{
info.insert({functors.size(), "处理日志的任务"});
functors.push_back(f1);
info.insert({functors.size(), "备份数据的任务"});
functors.push_back(f2);
info.insert({functors.size(), "处理网络链接的任务"});
functors.push_back(f3);
}
// int32_t: 进程pid,int32_t:该进程对应的管道写端fd
typedef pair<int32_t, int32_t> elem;
int processNum = 5;
void work(int blockFd)
{
cout << "进程[" << getpid() << "]" << " 开始工作" << endl;
// 子进程核心工作的代码
while (true)
{
// a.阻塞等待 b.获取任务信息
uint32_t operatorCode = 0;
ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
if (s == 0) break;
assert(s == sizeof(uint32_t));
(void)s;
// c.处理任务
if (operatorCode < functors.size()) functors[operatorCode]();
}
cout << "进程[" << getpid() << "]" << " 结束工作" << endl;
}
void createChildProcess(vector<elem> &assignMap)
{
// 创建procesNum个进程
for (int i = 0; i < processNum; i++)
{
// 定义保存管道fd的对象
int pipefd[2] = {0};
// 创建管道
pipe(pipefd);
// 创建子进程
pid_t id = fork();
if (id == 0)
{
// 子进程读取,r -> pipefd[0]
close(pipefd[1]);
// 子进程执行
work(pipefd[0]);
close(pipefd[0]);
exit(0);
}
// 父进程做的事情 -> pipefd[1]
close(pipefd[0]);
elem e(id, pipefd[1]);
assignMap.push_back(e);
}
}
// [子进程的pid,子进程的管道fd]
void blanceSendTask(const vector<elem> &processFds)
{
srand((long long)time(nullptr));
while(true)
{
sleep(1);
// 选择一个进程
uint32_t pick = rand() % processFds.size();
// 选择一个任务
uint32_t task = rand() % functors.size();
// 把任务给一个指定的进程
write(processFds[pick].second, &task, sizeof(task));
// 打印对应的提示信息
cout << "父进程指派任务->" << info[task]
<< "给进程:" << processFds[pick].first << " 编号:" << pick << endl;
}
}
void recycleResources(const vector<elem> &assignMap)
{
for (int i = 0; i < processNum; i++)
{
if (waitpid(assignMap[i].first, nullptr, 0) > 0)
cout << "wait for: pid = " << assignMap[i].first
<< " number: " << i << "wait sussess!" << endl;
close(assignMap[i].second);
}
}
int main()
{
vector<elem> assignMap;
// 加载任务列表
loadFunctor();
// 创建子进程
createChildProcess(assignMap);
cout << "create all process success!" << endl;
// 父进程,派发任务
blanceSendTask(assignMap);
// 回收资源
recycleResources(assignMap);
return 0;
}
总结
本文从代码角度详细介绍了一个匿名管道的实例–进程池,功能是父进程控制一批子进程完成对应的任务。大家有兴趣的话可以尝试用自己的方式实现这个功能。喜欢的话,欢迎点赞支持和关注~