原理
匿名管道可用于父子进程间的通讯,于是可以有父进程创建多个子进程形成进程池,并通过匿名管道文件向各个子进程派发任务
戳我去管道原理🔗
这里使用C++编写进程池代码,在程序中创建多个进程,并在父进程中使用自定义类channel用于描述和管理子进程,然后在task.hpp中模拟一些任务给主程序随机派发
代码
代码规范
这次对形参有了新的规范,这里用variable代指形参名
- 输入: const &variable
- 输出: *variable
- 输入输出: &variable
准备makefile文件
这里使用g++编译,规定语法标准为C++11
| 12
 3
 4
 5
 6
 
 | processPool:processPool.ccg++ -o $@ $^ -std=c++11
 
 .PHONY:clean
 clean:
 rm -rf processPool
 
 | 
准备任务文件
首先要准备前后需要用到的头文件,然后构建模拟任务,并提供加载任务列表的函数
task.hpp
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | #pragma once
 #include <vector>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <cassert>
 #include <iostream>
 #include <string>
 
 
 typedef void (*task_t)();
 
 void task1()
 {
 std::cout << "PVZ 刷新日志" << std::endl;
 }
 void task2()
 {
 std::cout<< "PVZ 生成阳光"<<std::endl;
 }
 void task3()
 {
 std::cout<<"PVZ 检测更新" <<std::endl;
 }
 void task4()
 {
 std::cout<<"PVZ 使用能量豆"<<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);
 }
 
 | 
编写主程序
主程序文件名为processPool.cc,我们按类和接口从上往下编程
描述和组织
我们把头文件准备好,然后定义最大进程数和创建任务列表,接着创建channel类来描述每个进程池中的子进程
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | #include "task.hpp"#include <ctime>
 #include <sys/stat.h>
 #include <sys/wait.h>
 
 
 const int processNum = 5;
 std::vector<task_t> tasks;
 
 
 class channel
 {
 public:
 channel(int cmdfd,pid_t slaverid,std::string& name)
 :_cmdfd(cmdfd),_slaverid(slaverid),_processname(name)
 {}
 
 public:
 int _cmdfd;
 pid_t _slaverid;
 std::string _processname;
 };
 
 | 
slaver函数
创建子进程后,子进程都进入slaver函数等待获取任务
这里统一采用输入重定向,在创建子进程后,进入slaver()前先把标准输入重定向到管道文件,然后从标准输入读取任务码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | void slaver(){
 while(true)
 {
 int cmdcode = 0;
 int n = read(0,&cmdcode,sizeof(int));
 if(n == sizeof(int))
 {
 
 if(cmdcode >=0 && cmdcode < tasks.size())
 {
 tasks[cmdcode]();
 }
 else
 {
 std::cout<<"wrong cmdcode: "<<cmdcode
 <<" max size: "<<tasks.size()<<std::endl;
 }
 }
 }
 }
 
 | 
InnitChannels
该函数用鱼创建子进程,创建子进程,并封装进频道类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | void InitChannels(std::vector<channel>* channels){
 for(int i = 0;i<processNum;i++)
 {
 int pipefd[2];
 int n = pipe(pipefd);
 assert(!n);
 (void)n;
 
 pid_t id = fork();
 if(id == 0)
 {
 close(pipefd[1]);
 dup2(pipefd[0],0);
 slaver();
 std::cout<<"proccess "<< getpid() << " quit"<<std::endl;
 exit(0);
 }
 
 close(pipefd[0]);
 std::string name = "process-"+std::to_string(i);
 channels->push_back(channel(pipefd[1],id,name));
 }
 }
 
 | 
channelDEBUG
这里封装一个用于测试创建频道的DEBUG函数
| 12
 3
 4
 5
 6
 7
 
 | void Debug(const std::vector<channel> channels){
 for(auto &c:channels)
 {
 std::cout<<c._cmdfd << " " << c._slaverid<< " " << c._processname << std::endl;
 }
 }
 
 | 
ctrlSlaver
这里封装一个ctrlSlaver用于控制子进程,也就是用于派发任务的函数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | void ctrlSlaver(const std::vector<channel> &channels){
 for(int i = 0;i<10;i++)
 {
 int cmdcode = rand() % tasks.size();
 int select_num = rand()%channels.size();
 std::cout<<"father say# "<<"taskcode: "<<cmdcode<<" send to "<<channels[select_num]._processname
 << std::endl;
 write(channels[select_num]._cmdfd,&cmdcode,sizeof(cmdcode));
 sleep(1);
 }
 }
 
 | 
QuitProcess
最后当然要有父进程控制关闭子进程,并回收僵尸进程
因为InitChannels中管道文件的处理比较粗糙,会发生如下图的关系,所以关闭时两步操作一定要放在两个循环中

| 12
 3
 4
 5
 6
 
 | void QuitProcess(const std::vector<channel>& channels){
 
 for(const auto &c:channels)close(c._cmdfd);
 for(const auto &c:channels)waitpid(c._slaverid,nullptr,0);
 }
 
 | 
main函数
经过上文一系列封装,main函数就可以简洁明了地描述子进程的运行过程了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | int main(){
 srand(time(nullptr) ^ getpid()^1023);
 std::vector<channel> channels;
 
 LoadTask(&tasks);
 InitChannels(&channels);
 
 
 
 
 
 ctrlSlaver(channels);
 
 
 QuitProcess(channels);
 
 return 0;
 }
 
 | 
源文件
戳我去github仓库🔗