代码的过程:
创建一个任务表,创建若干个子进程,他们依次和父进程建立通信信道,建立好管道关系后,父进程随机选择一个任务,再让随机一个子进程从管道中读取该任务并执行,完成要执行的轮次后,父进程关闭在管道中的所有写端,子进程而后被操作系统发送异常信号,所有子进程被终止,父进程再依次回收所有子进程。
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <cassert>
#include <string>
#include <vector>
#include <ctime>
#include <sys/wait.h>
#include <sys/types.h>
#define MakeSeed() srand((unsigned long)time(nullptr)^ getpid() ^ 0x171313 ^ rand()%1234)
#define PROCESS_NUM 5
//------------------------------子进程要完成的某种任务-------------------------------------------------------------
//函数指针 类型
typedef void(*func_t)();
void downLoadTask()
{
std::cout << getpid() << ": 下载任务\n" << std::endl;
sleep(1);//模拟下载任务
}
void ioTask()
{
std::cout << getpid() << ": IO任务\n" << std::endl;
sleep(1);//模拟IO任务
}
void flushTask()
{
std::cout << getpid() << ": 刷新任务\n" << std::endl;
sleep(1);//模拟刷新任务
}
//任务表
void loadTaskFunc(std::vector<func_t>* funcMap)
{
funcMap->push_back(downLoadTask);
funcMap->push_back(ioTask);
funcMap->push_back(flushTask);
}
//-----------------------------下面的代码是一个多进程程序----------------------------------------------------------
class SubEp //end point
{
public:
SubEp(pid_t subId,int writeFd)
:_subId(subId),_writeFd(writeFd)
{
char nameBuffer[1024];
snprintf(nameBuffer,sizeof nameBuffer,"process-%d[pid(%d)-fd(%d)]",num++,_subId,_writeFd);
_name = nameBuffer;
}
public:
std::string _name;
pid_t _subId;
int _writeFd;
static int num;
};
int SubEp::num = 0;
//获取命令码
int recvTask(int readFd)
{
int code = 0;
ssize_t s = read(readFd, &code, sizeof code);
if(s == 4)
return code;
else
return -1;;
}
//发送任务给进程
void sendTask(const SubEp& process,int taskNum)
{
std::cout << "send task num: "<< taskNum << " send to -> " << process._name << std::endl;
int n = write(process._writeFd,&taskNum,sizeof(taskNum));
assert(n == sizeof(int));
(void)n;
}
//创建子进程,建立父子通信信道
void creatSubProcess(std::vector<SubEp>* subs,std::vector<func_t>& funcMap)
{
for(int i = 0; i < PROCESS_NUM;++i)
{
int fds[2];
int n = pipe(fds);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0)
{
//子进程进行处理任务
close(fds[1]);
while(true)
{
//1. 获取命令码,如果没有发送,子进程应该阻塞
int commandCode = recvTask(fds[0]);
//2. 完成任务
if(commandCode >= 0 && commandCode < funcMap.size())
funcMap[commandCode]();
else if(commandCode == -1)
break;
}
exit(0);
}
close(fds[0]);
SubEp sub(id,fds[1]);
subs->push_back(sub);
}
}
//让随机一个子进程完成随机任务
void loadBlanceContrl(const std::vector<SubEp>& subs,const std::vector<func_t>& funcMap,int count)
{
int processNum = subs.size();
int taskNum = funcMap.size();
while(true)
{
//1. 选择一个子进程
int subIndex = rand()% processNum;
//2. 选择一个任务
int taskIndex = rand()% taskNum;
//3. 任务发送给选择的进程
sendTask(subs[subIndex],taskIndex);
sleep(1);
if(count != 0)
{
--count;
if(count == 0) break;
}
}
//关闭父进程在所有管道中的写端
for(int i = 0;i < processNum;++i)
close(subs[i]._writeFd);
}
//回收子进程
void waitProcess(const std::vector<SubEp>& processes)
{
int processNum = processes.size();
for(int i = 0; i < processNum;++i)
{
waitpid(processes[i]._subId,nullptr,0);
std::cout<< "wait sub process success ..."<< processes[i]._subId << std::endl;
}
}
int main()
{
MakeSeed();
//1.建立子进程并且建立和子进程通信的信道
// 加载方法表
std::vector<func_t> funcMap;
loadTaskFunc(&funcMap);
//创建子进程,并维护好父子通信信道
std::vector<SubEp> subs;
creatSubProcess(&subs,funcMap);
//2.走到这就是父进程,控制子进程,负载均衡的向子进程发送命令码
int taskCnt = 5; // = 0 :表示永远进行
loadBlanceContrl(subs,funcMap,taskCnt);
//3. 回收子进程信息
waitProcess(subs);
return 0;
}
操作演示:
但是这里有个问题需要知道,子进程会继承父进程文件描述符表的内容,那么后面的子进程,他的文件描述符表里也会有其他子进程的写端(因为我们没有关闭),虽然不影响现在的代码,但是如果你要是关闭一个写端回收一个子进程,那么此时就会出现问题。
具体问题看下图:父进程关闭了写端,此时子进程1应该是由操作系统发送异常信号,终止子进程1的,但是由于子进程2继承了父进程的写端,所以操作系统认为还有进程再往管道1内写数据,所以并不会对子进程1发送异常信号。
但是为什么这个代码没出问题呢?
因为固定的任务轮次完成后,父进程由前到后依次关闭了对所有子进程的写端,但是最后一个子进程中只有父进程的写端,写端关闭后,该进程就异常终止了,它里面保存的其他进程的写端也就关闭了,那么由后往前子进程也就依次的终止退出了。
如果你想让子进程按顺序依次回收,那么将下面的代码稍作修改就可以。
把所有子进程的写端保存下来,让下一个子进程把自己继承的所有写端关闭掉,这样就好了。
void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
std::vector<int> deleteFd;
for (int i = 0; i < PROCSS_NUM; i++)
{
int fds[2];
int n = pipe(fds);
assert(n == 0);
(void)n;
// 父进程打开的文件,是会被子进程共享的
// 你试着多想几轮
pid_t id = fork();
if (id == 0)
{
for(int i = 0; i < deleteFd.size(); i++) close(deleteFd[i]);
// 子进程, 进行处理任务
close(fds[1]);
while (true)
{
// 1. 获取命令码,如果没有发送,我们子进程应该阻塞
int commandCode = recvTask(fds[0]);
// 2. 完成任务
if (commandCode >= 0 && commandCode < funcMap.size())
funcMap[commandCode]();
else if(commandCode == -1) break;
}
exit(0);
}
close(fds[0]);
subEp sub(id, fds[1]);
subs->push_back(sub);
deleteFd.push_back(fds[1]);
}
}