目录
诶朋友,想象你走进一家超火爆的奶茶店。店里订单天山上雪花一般的多,要是每个订单都现招一个员工来做,那不乱套了嘛!这时,进程池技术就像这家奶茶店的 “高效秘籍”,今天咱就结合代码,唠唠这个神奇的 “秘籍” 是咋运作的。
奶茶店架构初览
员工团队搭建(进程池创建)
代码里有个Create
函数,它就好比奶茶店老板在组建员工团队。老板要先确定招多少人,代码里是const int num = 5
,这就是要创建 5 个子进程,组成进程池。
老板要给员工和自己弄个沟通渠道,代码里通过pipe
系统调用创建管道,就像拉了根 “通话线”,专门用来传订单(任务指令)。接着用fork
创建子进程,这就好比老板复制出一个个员工,每个员工都有自己的 “工作位”(进程 ID)。
在员工这边(子进程),要是之前有别的 “通话线”(旧管道),就得先关掉,然后专心用新的 “通话线” 接收订单。而老板(父进程)则把和员工沟通的 “通话线” 一端(写端文件描述符)存好,方便之后派活。
void Create(vector<channel> *c)
{
vector<int> old;
for (int i = 0; i < num; i++)
{
// 1. 定义并创建管道
int pipefd[2];
int n = pipe(pipefd);
assert(n == 0);
(void)n;
// 2. 创建进程
pid_t id = fork();
assert(id != -1);
// 3. 构建单向通信信道
if (id == 0) // child
{
if (!old.empty())
{
for (auto fd : old)
{
close(fd);
}
}
close(pipefd[1]);
dup2(pipefd[0], 0);
Work();
exit(0); // 会自动关闭自己打开的所有的fd
}
// father
close(pipefd[0]);
c->push_back(channel(pipefd[1], id));
old.push_back(pipefd[1]);
// childid, pipefd[1]
}
}
菜单与制作规范(任务定义与管理)
Init
类就像是奶茶店的 “菜单与制作规范手册”。它定义了各种任务,像g_download_code
(对应下载任务,好比制作珍珠奶茶)、g_printlog_code
(打印日志任务,类似记录每天的销售情况)、g_push_videostream_code
(推送视频流任务,仿佛在社交媒体上宣传新品)。
“手册” 里还有个tasks
向量,存着每个任务具体咋做的方法,就像奶茶配方。构造函数把这些 “配方” 加进去,还像每天开店前摇骰子决定新品一样,用srand(time(nullptr))
初始化随机数种子,方便随机选任务来做。
class Init
{
public:
// 任务码
const static int g_download_code = 0;
const static int g_printlog_code = 1;
const static int g_push_videostream_code = 2;
// 任务集合
vector<task_t> tasks;
public:
Init()
{
tasks.push_back(Download);
tasks.push_back(PrintLog);
tasks.push_back(PushVideoStream);
srand(time(nullptr));
}
bool CheckSafe(int code)
{
if (code >= 0 && code < tasks.size())
return true;
else
return false;
}
void RunTask(int code)
{
return tasks[code]();
}
int SelectTask()
{
return rand() % tasks.size();
}
string ToDesc(int code)
{
switch (code)
{
case g_download_code:
return "Download";
case g_printlog_code:
return "PrintLog";
case g_push_videostream_code:
return "PushVideoStream";
default:
return "Unknow";
}
}
};
Init init;
订单派送(任务分发)
SendCommand
函数是店里的 “订单派送员”。它从 “菜单” 里随机挑个任务(订单),就像顾客随机点奶茶。然后按顺序把任务派给员工(子进程),通过 “通话线”(信道控制文件描述符)把任务代码发过去。每派一个单,还得等会儿,就像做一杯奶茶需要时间,这里是sleep(1)
模拟这个间隔。
void SendCommand(const vector<channel> &c, int count)
{
int pos = 0;
while (count--)
{
// 1. 选择任务
int command = init.SelectTask();
// 2. 选择信道(进程)
const auto &channel = c[pos++];
pos %= c.size();
// 3. 发送任务
write(channel.ctrlfd, &command, sizeof(command));
sleep(1);
}
cout << "SendCommand done" << endl;
}
打烊收工(资源回收)
ReleaseChannels
函数在打烊时登场啦!它就像老板检查店里,先把和员工沟通的 “通话线”(控制文件描述符)关掉,再等员工都完成手头工作下班(用waitpid
等待子进程结束),保证店里收拾得干干净净,不留下任何 “尾巴”(避免僵尸进程)。
void ReleaseChannels(std::vector<channel> c)
{
int num = c.size() - 1;
for (; num >= 0; num--)
{
close(c[num].ctrlfd);
waitpid(c[num].workerid, nullptr, 0);
}
}
Work 函数:员工的 “工作日常”
Work
函数是员工的 “工作日常”。员工一直在那等着订单,通过 “热线”(标准输入,被重定向成管道读端)收订单代码。收到完整订单(数据长度对得上),先检查能不能做(通过init.CheckSafe
),能做就开工(init.RunTask
)。要是 “热线” 没声了(数据长度为 0,老板关了管道),就下班,打印 “child quit”,结束工作。
void Work()
{
while (true)
{
int code = 0;
ssize_t n = read(0, &code, sizeof(code));
if (n == sizeof(code))
{
if (!init.CheckSafe(code))
continue;
init.RunTask(code);
}
else if (n == 0)
{
break;
}
else
{
// do nothing
}
}
cout << "child quit" << endl;
}
进程池这招为啥这么牛
省资源:员工别老换来换去
进程池避免了频繁创建和销毁进程,就像奶茶店不能一会儿招一堆人,一会儿又全辞退,太浪费精力和成本了。有了进程池,员工一直在那,随时能接单干活,系统资源利用率蹭蹭往上涨。
干活快:大家一起做奶茶
多个子进程(员工)能同时处理不同任务(订单),就像奶茶店多个员工同时做不同口味的奶茶,大大加快了任务处理速度,顾客不用等太久,系统整体性能就上去了。
好管理:老板心里有底
通过进程池,能轻松控制同时干活的人数(并发度)。就像奶茶店老板知道店里最多能容下多少员工同时工作,不会因为人太多乱糟糟,也不会资源耗尽,保证店里稳稳当当运营。
生活里的进程池
服务器:网站背后的 “超级奶茶店”
在服务器端编程里,进程池超有用。比如网站像个超大型奶茶店,每天有成千上万个顾客(客户端)来访问,发请求(订单)。进程池能让服务器高效处理这些请求,就像奶茶店快速做出一杯杯奶茶,让用户体验超棒。
数据处理:大数据的 “加工车间”
处理大规模数据时,进程池就像大数据的 “加工车间”。比如要分析海量用户数据,或者处理一堆图片,把任务分给不同进程并行处理,就像奶茶店不同员工同时做不同口味的奶茶,速度快得飞起。
任务调度:分布式系统的 “指挥家”
在分布式系统里,进程池像个 “指挥家”。把任务合理分配到不同节点(就像把订单分给不同员工),让整个系统和谐运转,就像奶茶店每个员工都清楚自己该干啥,店里忙而不乱。
代码模拟进程池
ProcessPool.hpp
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "task.hpp"
using namespace std;
// 定义要创建的子进程个数
const int num = 5;
static int number = 1;
class channel
{
public:
channel(int fd, pid_t id) : ctrlfd(fd), workerid(id)
{
name = "channel-" + to_string(number++);
}
public:
int ctrlfd;
pid_t workerid;
string name;
};
void Work()
{
while (true)
{
int code = 0;
ssize_t n = read(0, &code, sizeof(code));
if (n == sizeof(code))
{
if (!init.CheckSafe(code))
continue;
init.RunTask(code);
}
else if (n == 0)
{
break;
}
else
{
// do nothing
}
}
cout << "child quit" << endl;
}
void Create(vector<channel> *c)
{
vector<int> old;
for (int i = 0; i < num; i++)
{
// 1. 定义并创建管道
int pipefd[2];
int n = pipe(pipefd);
assert(n == 0);
(void)n;
// 2. 创建进程
pid_t id = fork();
assert(id != -1);
// 3. 构建单向通信信道
if (id == 0) // child
{
if (!old.empty())
{
for (auto fd : old)
{
close(fd);
}
}
close(pipefd[1]);
dup2(pipefd[0], 0);
Work();
exit(0); // 会自动关闭自己打开的所有的fd
}
// father
close(pipefd[0]);
c->push_back(channel(pipefd[1], id));
old.push_back(pipefd[1]);
// childid, pipefd[1]
}
}
// 发送任务
void SendCommand(const vector<channel> &c, int count)
{
int pos = 0;
while (count--)
{
// 1. 选择任务
int command = init.SelectTask();
// 2. 选择信道(进程)
const auto &channel = c[pos++];
pos %= c.size();
// 3. 发送任务
write(channel.ctrlfd, &command, sizeof(command));
sleep(1);
}
cout << "SendCommand done" << endl;
}
// 回收资源
void ReleaseChannels(std::vector<channel> c)
{
int num = c.size() - 1;
for (; num >= 0; num--)
{
close(c[num].ctrlfd);
waitpid(c[num].workerid, nullptr, 0);
}
}
int main()
{
vector<channel> channels;
// 1. 创建进程
Create(&channels);
// 2. 发送任务
SendCommand(channels, 10);
// 3. 回收资源
ReleaseChannels(channels);
return 0;
}
task.hpp
#pragma once
#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
#include <unistd.h>
using namespace std;
typedef function<void()> task_t;
void Download()
{
cout << "this is download" << endl;
}
void PrintLog()
{
cout << "this is PrintLog" << endl;
}
void PushVideoStream()
{
cout << "this is pushvideo" << endl;
}
class Init
{
public:
// 任务码
const static int g_download_code = 0;
const static int g_printlog_code = 1;
const static int g_push_videostream_code = 2;
// 任务集合
vector<task_t> tasks;
public:
Init()
{
tasks.push_back(Download);
tasks.push_back(PrintLog);
tasks.push_back(PushVideoStream);
srand(time(nullptr));
}
bool CheckSafe(int code)
{
if (code >= 0 && code < tasks.size())
return true;
else
return false;
}
void RunTask(int code)
{
return tasks[code]();
}
int SelectTask()
{
return rand() % tasks.size();
}
string ToDesc(int code)
{
switch (code)
{
case g_download_code:
return "Download";
case g_printlog_code:
return "PrintLog";
case g_push_videostream_code:
return "PushVideoStream";
default:
return "Unknow";
}
}
};
Init init;
Makefile
ProcessPool:ProcessPool.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf ProcessPool