前言
之前我们学习了进程通过匿名管道进行通信,实现了两个进程的数据传输。
如果我们管理的是很多个进程,通过管道发送指令,因为如果管道中没有数据,读端必须等待,也就是被管理的进程们都在等待我发送的指令,那么我们可以通过特定的通讯方式,对进程实施控制,也就是让进程根据传输的指令去完成相应的操作。
一、进程池概念
我们知道,系统资源的获取是有成本的,比如我们创建进程,需要花一定的时间去完成,比如现在我们有一些比较重要的任务需要处理,如果等待任务到来,再创建进程去处理任务,时间上会慢一点,如果我们提前将进程创建好,任务到来,我们直接对进程分派任务,这样就能节省时间,这些提前创建好并被管理的进程,有任务来就分派执行,我们可以称之为进程池。
打个比方,比如说你喝娃哈哈矿泉水,如果你感觉到口渴了,才去外面超市买娃哈哈矿泉水,这样成本是不是比较高,有点浪费时间,但是如果你提前在家里面放上一箱娃哈哈矿泉水,渴了就喝,顺手就拿的事情,效率就提高了,这也相当于把矿泉水进行池化了。
二、进程池实现
主要思路是先创建进程和信道,再发布任务,最后等待关闭父进程的写进程,让子进程read读到0,就退出子进程。最后父进程进行资源回收。
具体可以看代码注释,这里就不多BB了
ProcessPool.cpp
#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;
#define NUM 5
class channel
{
public:
channel(string name, int fd, pid_t pid)
: _name(name), _fd(fd), _pid(pid)
{}
public:
string _name;
int _fd;
pid_t _pid;
};
void Work()
{
while (true)
{
int code = 0;
ssize_t n = read(0, &code, sizeof(code));
if (n == sizeof(code))
{
if (!init.Check(code))
{
cout << "任务码不合法,执行失败" << endl;
continue;
}
init.RunTask(code);
}
else if (n == 0)
break;
}
}
void CreatProcess(vector<channel> &v)
{
vector<int> vfd; // 存放父进程的写信道的fd
for (int i = 0; i < NUM; i++)
{
// 创建管道
int pipefd[2];
int n = pipe(pipefd);
assert(n == 0);
// 创建进程
pid_t id = fork();
if (id < 0)
{
perror("fork");
exit(1);
}
// 构建信道
else if (id == 0)
{
// 子进程
vfd.push_back(pipefd[1]);
for (auto &fd : vfd) // 关闭子进程继承的写信道
{
close(fd);
}
dup2(pipefd[0], 0);
Work();
exit(0);
}
// 父进程
close(pipefd[0]);
v.push_back(channel(to_string(i + 1) + "号信道", pipefd[1], id));
}
}
void PrintChannel(const vector<channel> &v)
{
for (auto &c : v)
{
cout << c._name << "," << c._fd << "," << c._pid << endl;
}
}
void SendTask(const vector<channel> &v, bool flag, int num = 1)
{
int pos = 0;
while (true)
{
int task = init.SelectTask(); // 选择任务
pos %= v.size(); // 选择信道轮流执行任务
channel c = v[pos++];
cout<<"发送信息"<<init.ToDesc(task)<<"给"<<c._name<<",pid是"<<c._pid<<endl;
write(c._fd, &task, sizeof(task)); // 发送任务
if (!flag)
{
if (--num <= 0)
break;
}
sleep(1);
}
sleep(1);
cout << "发送任务完成" << endl;
}
void WaitPorcess(vector<channel> v)
{
for (auto &c : v)
{
close(c._fd);
pid_t rid = waitpid(c._fd, nullptr, 0);
cout<<"等待子进程"<<c._pid<<"成功"<<endl;
}
}
int main()
{
vector<channel> channels;
// 创建进程和信道
CreatProcess(channels);
// PrintChannel(channels); //打印测试
// 发送任务
const bool always_loop = true;
SendTask(channels, !always_loop, 10);
// 进程等待回收
WaitPorcess(channels);
return 0;
}
Task.hpp
#pragma once
#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
using namespace std;
typedef function<void()> task_t;
void Download()
{
cout << "我正在下载,"
<< "pid:" << getpid() << endl;
}
void Print()
{
cout << "我正在打印,"
<< "pid:" << getpid() << endl;
}
void PlayVideo()
{
cout << "我正在播放,"
<< "pid:" << getpid() << endl;
}
class Init
{
public:
Init()
{
tasks.push_back(Download);
tasks.push_back(Print);
tasks.push_back(PlayVideo);
srand(time(nullptr));
}
void RunTask(int code)
{
tasks[code]();
}
string ToDesc(int code)
{
switch (code)
{
case 0:
return "Download";
case 1:
return "Print";
case 2:
return "PlayVideo";
default:
return "Unkonw";
}
}
bool Check(int code)
{
return code >= 0 && code < tasks.size();
}
int SelectTask()
{
return rand() % tasks.size();
}
public:
vector<task_t> tasks;
// 任务码
const int download_code = 0;
const int print_code = 1;
const int PlayVideo_code = 2;
};
Init init;
运行结果如下。