我们已经认识到了匿名管道,这篇博客我们不仅要将匿名管道的一些其他知识点讲解,还要写出属于自己的进程池,话不多说,开始我们今天的学习啦~~
1.管道内存上限
我们之前一直说管道并不是无限内存的,而是可以被写满的,那么我们怎么才知道管道究竟有多大内存呢,下面我们来看看
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
void ChildWrite(int wfd)
{
char c = 0;
int cnt = 0;
while (true)
{
write(wfd, &c, 1);
std::cout << "cnt : " << cnt++ << std::endl;
}
}
void FutherRead(int rfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
//sleep(5);
buffer[0] = '\0';
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = '\0';
std::cout << buffer << std::endl;
}
/*
else if (n == 0)
{
std::cout << "没有内容 : " << n << std::endl;
std::cout << "子进程退出,我也退出" << n << std::endl;
break;
}
else
{
break;
}*/
break;
}
}
int main()
{
// 创建管道
int pfd[2] = {0};
int n = pipe(pfd);
if (n == -1)
{
std::cerr << "pipe error!" << std::endl;
return 1;
}
std::cout << "pfd[0] : " << pfd[0] << std::endl;
std::cout << "pfd[1] : " << pfd[1] << std::endl;
// 一般来说,pfd[0] -> 读端 pfd[1] -> 写端
// 创建子进程
pid_t id = fork();
if (id == 0)
{
// 关闭子进程读端
close(pfd[0]);
// 子进程向管道写入内容
ChildWrite(pfd[1]);
// 关闭子进程写端
close(pfd[1]);
}
// 关闭父进程写端
close(pfd[1]);
// 父进程读取管道内容
//FutherRead(pfd[0]);
//等待子进程
waitpid(id, nullptr, 0);
// 关闭父进程读端
close(pfd[0]);
return 0;
}

这个代码我们只写不读,且一个一个字符写入,同时让cnt++,发现到65535的时候(即写入了65536个字节)时,管道被写满,而65535 / 1024 = 64,所以在我的系统下,管道内存为64KB
2.进程池
1.什么是进程池

如图,我们创建一个父进程,父进程创建了很多通道,之后fork子进程,关闭父进程读端与子进程写端,那么此时我们只需要写入code(任务码),就可以控制不同的子进程来完成我们想完成的功能,这个就叫做进程池,就比如我们写的string,当size满了的时候,我们不是让capacity一个一个加上去的,而是直接乘以1.5or2倍,这样可以提高效率,随时可以使用--所以进程池的优点:提前预分配资源,避免频繁创建 / 销毁的开销,实现资源复用、提升效率
2.操作
| 操作 | 目的 & 原理 |
|---|---|
| 父进程创建多个管道 | 为每个子进程分配专属单向通信通道(一对一管道),用于父进程向子进程发送「任务码」(比如 1 = 执行计算、2 = 执行文件读写、3 = 执行网络请求); |
| fork 多个子进程 | 预创建一批子进程(进程池),避免每次有任务才 fork(fork 进程的开销极大:需要复制进程地址空间、页表、文件描述符等); |
| 父关读端、子关写端 | 确保每个管道是「父写、子读」的单向通信,避免同一管道双向读写导致数据混乱,同时符合管道 "半双工" 的特性; |
| 父写任务码→子执行 | 子进程启动后,阻塞在管道的read()调用上(等待任务);父进程根据需求,向对应管道写入任务码,子进程拿到后执行指定功能,执行完继续阻塞等待下一个任务 ------ 这就是进程池 "随时可用、复用进程" 的核心。 |
3.实现
Makefile:
bash
processpool : Main.cc
g++ $^ -o $@ -std=c++11
.PHONEY : clean
clean :
rm -f processpool
很平常的Makefile实现,这个就不详细讲解啦
ProcessPool.hpp:
cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include "Task.hpp"
class Channel // 通道
{
private:
int _wfd;
int _id;
std::string _name;
public:
Channel(int wfd, int id)
: _wfd(wfd), _id(id)
{
_name = "channel -- " + std::to_string(_wfd) + " -- " + std::to_string(_id);
}
~Channel()
{
}
std::string Name()
{
return _name;
}
void Send(int taskcode)
{
int n = write(_wfd, &taskcode, sizeof(taskcode));
(void)n;
}
void CloseFatherProcess()
{
std::cout << "父进程写端 : " << _wfd << " 关闭" << std::endl;
close(_wfd);
}
void WaitChildProcess()
{
pid_t rid = waitpid(_id, nullptr, 0);
(void)rid;
}
};
class ChannelManager // 管理通道
{
private:
std::vector<Channel> _channels;
int _next;
public:
ChannelManager()
: _next(0)
{
}
~ChannelManager()
{
}
void Insert(int wfd, pid_t id)
{
_channels.push_back({wfd, id});
}
void Print()
{
for (auto &e : _channels)
{
std::cout << e.Name() << std::endl;
}
}
Channel& Select()
{
auto& e = _channels[_next];
_next++;
_next %= _channels.size();
return e;
}
void CloseSubFatherProcess()
{
for (auto& e : _channels)
{
e.CloseFatherProcess();
}
}
void WaitSubChildProcess()
{
for (auto& e : _channels)
{
e.WaitChildProcess();
}
}
};
class ProcessPool
{
public:
ProcessPool(int num = 5)
: _process_num(num)
{
_tm.Register(PrintLog);
_tm.Register(DownLoad);
_tm.Register(UpLoad);
}
~ProcessPool()
{
}
void Work(int rfd)
{
/*while (true)
{
std::cout << "我是子进程, 我的rfd : " << rfd << std::endl;
sleep(5);
}*/
while (true)
{
int taskcode = 0;
ssize_t n = read(rfd, &taskcode, sizeof(taskcode));
if (n > 0)
{
if (n != sizeof(taskcode))
continue;
std::cout << "子进程[" << getpid() << "]收到一个任务码 : " << taskcode << std::endl;
_tm.Execute(taskcode);
}
else if (n == 0)
{
std::cout << "子进程[" << getpid() << "]退出" << std::endl;
break;
}
else
{
std::cout << "读取错误" << std::endl;
break;
}
}
}
// 创建(初始化)
bool Start()
{
for (int i = 0; i < _process_num; i++)
{
// 创建管道
int pipefd[2] = {0};
int n = pipe(pipefd);
if (n < 0)
return false;
// 创建子进程
pid_t id = fork();
if (id < 0)
{
return false;
}
else if (id == 0)
{
// 子进程
// 关闭子进程读端
close(pipefd[1]);
Work(pipefd[0]);
exit(0);
}
else
{
// 父进程
// 关闭父进程读端
close(pipefd[0]);
_cm.Insert(pipefd[1], id);
}
}
return true;
}
void DeBug()
{
_cm.Print();
}
void Run()
{
//随机选择一个任务
int taskcode = _tm.Code();
//轮询选择一个子进程来执行任务
auto& e = _cm.Select();
std::cout << "选择了一个子进程 : " << e.Name() << std::endl;
//发射任务
std::cout << "发射了一个任务码 : " << taskcode << std::endl;
e.Send(taskcode);
}
void Stop()
{
//关闭所有父进程写端
_cm.CloseSubFatherProcess();
//等待所有子进程
_cm.WaitSubChildProcess();
}
private:
ChannelManager _cm;
TaskManager _tm;
int _process_num;
};
1. Channel 核心
- 成员:
_wfd(父进程管道写端)、_child_id(对应子进程 PID)------ 这是父进程管理子进程的两个核心标识; - 核心方法:
Send(发任务码)、CloseWriteFd(关写端)、WaitChild(等子进程)------ 封装单个子进程的通信和生命周期管理。
2. ChannelManager 核心
- 容器:
vector<Channel> _channels------ 批量管理所有子进程的 Channel,替代逐个维护的繁琐; - 核心逻辑:
SelectChannel轮询算法 ------ 保证任务均匀分发给每个子进程,体现 "池化复用" 思想。
3. ProcessPool 核心
- 启动(
Start):循环创建管道 + fork 子进程,严格遵循 "父关读端、子关写端" 的管道单向通信规则; - 运行(
RunTask):整合 "任务生成 + 通道选择 + 任务发送",体现进程池 "主进程分发、子进程执行" 的核心模式; - 停止(
Stop):先关所有写端(触发子进程 EOF),再 waitpid 等待 ------ 完整的资源回收逻辑,避免僵尸进程。
Main.cc:
cpp
#include "ProcessPool.hpp"
int main()
{
//初始
ProcessPool p(5);
//开始
p.Start();
//调试
//p.DeBug();
//运行
int cnt = 10;
while (cnt--)
{
//选择一个通道
p.Run();
sleep(1);
std::cout << "---------------------------" << std::endl;
}
//结束
p.Stop();
return 0;
}
Task.hpp:
cpp
#include <iostream>
#include <vector>
#include <ctime>
void PrintLog()
{
std::cout << "我是一个打印日志的任务" << std::endl;
}
void DownLoad()
{
std::cout << "我是一个下载的任务" << std::endl;
}
void UpLoad()
{
std::cout << "我是一个上传的任务" << std::endl;
}
typedef void (*task_t)();
class TaskManager
{
public:
TaskManager()
{
srand(time(nullptr));
}
void Register(task_t t)
{
_tasks.push_back(t);
}
int Code()
{
return rand() % _tasks.size();
}
void Execute(int taskcode)
{
if (taskcode >= 0 && taskcode < _tasks.size())
{
_tasks[taskcode]();
}
}
~TaskManager()
{}
private:
std::vector<task_t> _tasks;
};