Linux——利用命名管道创建进程池
我们之前简单介绍了一下命名管道,我们今天来利用命名管道来创建一个进程池,之前我们利用过匿名管道创建过进程池,如果有小伙伴感兴趣可以点击这里:
这里其实没有啥新鲜的事情,但是通过这个可以帮助我们在面对问题时如何着手一步一步解决,培养我们的能力:
第一步:创建管道
进程池的第一步,你得有管道呀,所以我们编写一个函数来创建一批命名管道:
#pragma once
#include<sstream>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
//这里我们先创建5个命名管道
#define MY_CHANNEL_NUMBER 10
//管道的基本名字
const std::string base_name = "../my_fifo";
//创建管道
void CreateChannels()
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
std::string name = ss.str();
if(mkfifo(name.c_str(),0666) == -1)
{
perror("create channels fail");
continue;
}
}
}
我们在主函数中测试一下:
#include"ProcessPool.hpp"
int main()
{
CreateChannels();
}
这个时候有一个问题,如果我们的管道已经创建好了,那么重复创建肯定是不行的,这个时候我们用errno(C/C++ 编程中用于表示系统级错误的一个全局变量)来保存我们的错误代号,如果是管道已经存在,errno会储存错误信息,帮助我们判断:
#pragma once
#include<sstream>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<iostream>
#include<cerrno>
//这里我们先创建5个命名管道
#define MY_CHANNEL_NUMBER 10
//管道的基本名字
const std::string base_name = "../my_fifo";
//创建管道
void CreateChannels()
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
std::string name = ss.str();
if(mkfifo(name.c_str(),0666) == -1)
{
if(errno != EEXIST)
{
perror("create channels fail");
exit(EXIT_FAILURE);
}
else
{
std::cout<<"channel has existed"<<std::endl;
}
}
else
{
//如果你成功什么都不做
}
}
}
第二步:批量删除管道
现在我们创建好了管道,我们如果不想要了,我们要把它们删除。所以我们编写一个函数来批量删除管道:
void DeletePipes()
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
std::string name = ss.str();
if(unlink(name.c_str())==-1)
{
if(errno == ENOENT) //如果管道本身不存在
{
std::cout<<name<<"not exits"<<std::endl;
}
else
{
perror("unlink fail");
exit(EXIT_FAILURE);
}
}
}
}
我们测试一下:
#include"ProcessPool.hpp"
int main()
{
//创建管道
CreateChannels();
//删除管道
DeletePipes();
}
看到管道确实被删除了。
第三步:封装管道,使之容易被管理
不知道大家发现没有,我们在创建管道,删除管道的时候还要自己写管道路径
这样子稍微有点麻烦,既然这样,我们还不如把管道设计称为一个类,方便我们管理:
#pragma once
#include<sstream>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<iostream>
#include<cerrno>
#include<vector>
//这里我们先创建5个命名管道
#define MY_CHANNEL_NUMBER 10
//管道的基本名字
const std::string base_name = "../my_fifo";
//管道类
struct Channel
{
Channel(const std::string fifo_name)
:fifo_path(fifo_name)
{
}
// 管道路径
std::string fifo_path;
};
//创建管道
void CreateChannels(std::vector<Channel>* channels)
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
//创建Channel
Channel channel(ss.str()); //初始化一个chennel(管道)
if(mkfifo(channel.fifo_path.c_str(),0666) == -1)
{
if(errno != EEXIST)
{
perror("create channels fail");
break;
}
else
{
std::cout<<"channel has existed"<<std::endl;
channels->push_back(channel); //已经存在也要放进去
}
}
else
{
//如果你成功,则添加到vector里
channels->push_back(channel);
}
}
}
void DeletePipes(std::vector<Channel>& channels)
{
for(auto& e : channels)
{
if(unlink(e.fifo_path.c_str())==-1)
{
if(errno == ENOENT) //如果管道本身不存在
{
std::cout<<e.fifo_path<<"not exits"<<std::endl;
}
else
{
perror("unlink fail");
exit(EXIT_FAILURE);
}
}
else
{
std::cout<<"suecessful delete"<<std::endl;
}
}
}
第四步:模拟进程通信
我们现在有了管道,现在我们模拟一下进程之间的通信,这里我们就用父子进程进行通信,父进程往管道写入命令,子进程从管道当中读取并执行命令:
void DoingWork(const std::vector<Channel>& channels,const std::string work_name)
{
//选择一个管道进行工作
Channel one = ChooseChannel(channels);
//创建子进程
pid_t id = fork();
if(id == 0)
{
if(access(one.fifo_path.c_str(),F_OK) == -1)
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);
}
else
{
int rfd = open(one.fifo_path.c_str(),O_RDONLY);
if(rfd < 0)
{
perror("read fail");
exit(EXIT_FAILURE);
}
else
{
//子进程从管道当中读数据
char buffer[1024];
ssize_t number = read(rfd,buffer,sizeof(buffer));
if(number > 0)
{
buffer[number] = '\0';
}
//读取之后执行任务
Work(buffer);
//任务结束后关闭读端
close(rfd);
}
}
}
else if(id > 0) // 父进程
{
//以写方式打开
if(access(one.fifo_path.c_str(),F_OK) == -1)
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);;
}
else
{
int wfd = open(one.fifo_path.c_str(),O_WRONLY);
if(wfd < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
else
{
//往管道里面写入
//我们用一个vector存放string来模拟任务的场景
ssize_t number = write(wfd,work_name.c_str(),strlen(work_name.c_str()));
if(number < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
//关闭读端
close(wfd);
//回收子进程
waitpid(id,nullptr,0);
}
}
}
else
{
perror("fork fail");
exit(EXIT_FAILURE);
}
}
选择管道函数和选择任务函数
const Channel& ChooseChannel(const std::vector<Channel>& channels)
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, channels.size()-1);
// 生成随机数并输出
int number = dis(gen);
return channels[number];
}
const std::string SeleteWork()
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, task_list.size()-1);
// 生成随机数并输出
int number = dis(gen);
return task_list[number];
}
这里为了简便,我使用了vector储存字符串,来模拟任务列表,SeleteWork负责从中挑选工作:
//用来模拟任务
std::vector<std::string> task_list = {"dowload","movie","music","games","learning"};
整体代码
#pragma once
#include<sstream>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<iostream>
#include<cerrno>
#include<vector>
#include<sys/wait.h>
#include<random>
#include<cstring>
#include<ctime>
//这里我们先创建5个命名管道
int MY_CHANNEL_NUMBER = 10;
//用来模拟任务
std::vector<std::string> task_list = {"dowload","movie","music","games","learning"};
//管道的基本名字
const std::string base_name = "../my_fifo";
//管道类
struct Channel
{
Channel(const std::string fifo_name)
:fifo_path(fifo_name)
{
}
// 管道路径
std::string fifo_path;
};
//创建管道
void CreateChannels(std::vector<Channel>* channels)
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
//创建Channel
Channel channel(ss.str()); //初始化一个chennel(管道)
if(mkfifo(channel.fifo_path.c_str(),0666) == -1)
{
if(errno != EEXIST)
{
perror("create channels fail");
break;
}
else
{
std::cout<<"channel has existed"<<std::endl;
channels->push_back(channel); //已经存在也要放进去
}
}
else
{
//如果你成功,则添加到vector里
channels->push_back(channel);
}
}
}
void DeletePipes(std::vector<Channel>& channels)
{
for(auto& e : channels)
{
if(unlink(e.fifo_path.c_str())==-1)
{
if(errno == ENOENT) //如果管道本身不存在
{
std::cout<<e.fifo_path<<" not exits"<<std::endl;
}
else
{
perror("unlink fail");
exit(EXIT_FAILURE);
}
}
else
{
std::cout<<"suecessful delete"<<std::endl;
MY_CHANNEL_NUMBER--;
if(MY_CHANNEL_NUMBER == 0)
{
std::cout<<"channels have cleared"<<std::endl;
exit(EXIT_SUCCESS);
}
}
}
}
const Channel& ChooseChannel(const std::vector<Channel>& channels)
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, channels.size()-1);
// 生成随机数并输出
int number = dis(gen);
return channels[number];
}
void Work(const char* buffer)
{
std::cout<<"doing "<<buffer<<std::endl;
}
const std::string SeleteWork()
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, task_list.size()-1);
// 生成随机数并输出
int number = dis(gen);
return task_list[number];
}
void DoingWork(const std::vector<Channel>& channels,const std::string work_name)
{
//选择一个管道进行工作
Channel one = ChooseChannel(channels);
//创建子进程
pid_t id = fork();
if(id == 0)
{
if(access(one.fifo_path.c_str(),F_OK) == -1)
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);
}
else
{
int rfd = open(one.fifo_path.c_str(),O_RDONLY); //判断管道是否存在,不存在则退出,存在则打开
if(rfd < 0)
{
perror("read fail");
exit(EXIT_FAILURE);
}
else
{
//子进程从管道当中读数据
char buffer[1024];
ssize_t number = read(rfd,buffer,sizeof(buffer));
if(number > 0)
{
buffer[number] = '\0';
}
//读取之后执行任务
Work(buffer);
//任务结束后关闭读端
close(rfd);
}
}
}
else if(id > 0) // 父进程
{
//以写方式打开
if(access(one.fifo_path.c_str(),F_OK) == -1)
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);;
}
else
{
int wfd = open(one.fifo_path.c_str(),O_WRONLY);
if(wfd < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
else
{
//往管道里面写入
//我们用一个vector存放string来模拟任务的场景
ssize_t number = write(wfd,work_name.c_str(),strlen(work_name.c_str()));
if(number < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
//关闭读端
close(wfd);
//回收子进程
waitpid(id,nullptr,0);
}
}
}
else
{
perror("fork fail");
exit(EXIT_FAILURE);
}
}
#include"ProcessPool.hpp"
int main()
{
srand(time(0));
std::vector<Channel> channels;
//创建管道
CreateChannels(&channels);
//进行交互
for(int i = 0; i < 10; i++)
{
std::string work = SeleteWork();
DoingWork(channels,work);
}
//删除管道
DeletePipes(channels);
}
最后一步:实现类的封装
最后,我们这些过程可以都包含在主程序类中,封装所有函数,方便维护:
class MainProcess //主程序类
{
public:
private:
std::vector<Channel> channels;
};
公共接口放我们所有的函数(除了work函数):
#pragma once
#include<sstream>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<iostream>
#include<cerrno>
#include<vector>
#include<sys/wait.h>
#include<random>
#include<cstring>
#include<ctime>
//这里我们先创建5个命名管道
int MY_CHANNEL_NUMBER = 10;
//用来模拟任务
std::vector<std::string> task_list = {"dowload","movie","music","games","learning"};
//管道的基本名字
const std::string base_name = "../my_fifo";
//管道类
struct Channel
{
Channel(const std::string fifo_name)
:fifo_path(fifo_name)
{
}
// 管道路径
std::string fifo_path;
};
void Work(const char* buffer)
{
std::cout<<"doing "<<buffer<<std::endl;
}
class MainProcess //主程序类
{
public:
//创建管道
void CreateChannels()
{
for(int i = 0; i < MY_CHANNEL_NUMBER; i++)
{
std::stringstream ss; //标准流对象
ss<< base_name <<"_"<< i; //在基本名字之后加上序号来给管道标号
//创建Channel
Channel channel(ss.str()); //初始化一个chennel(管道)
if(mkfifo(channel.fifo_path.c_str(),0666) == -1)
{
if(errno != EEXIST)
{
perror("create channels fail");
break;
}
else
{
std::cout<<"channel has existed"<<std::endl;
channels.push_back(channel); //已经存在也要放进去
}
}
else
{
//如果你成功,则添加到vector里
channels.push_back(channel);
}
}
}
void DeletePipes()
{
for(auto& e : channels)
{
if(unlink(e.fifo_path.c_str())==-1)
{
if(errno == ENOENT) //如果管道本身不存在
{
std::cout<<e.fifo_path<<" not exits"<<std::endl;
}
else
{
perror("unlink fail");
exit(EXIT_FAILURE);
}
}
else
{
std::cout<<"suecessful delete"<<std::endl;
MY_CHANNEL_NUMBER--;
if(MY_CHANNEL_NUMBER == 0)
{
std::cout<<"channels have cleared"<<std::endl;
exit(EXIT_SUCCESS);
}
}
}
}
const Channel& ChooseChannel()
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, channels.size()-1);
// 生成随机数并输出
int number = dis(gen);
return channels[number];
}
const std::string SeleteWork()
{
// 创建一个随机数引擎
std::random_device rd; // 用于获取高质量的随机数种子
std::mt19937 gen(rd()); // 使用随机设备生成的种子初始化MT19937引擎
// 创建一个均匀分布的整数分布对象
std::uniform_int_distribution<> dis(0, task_list.size()-1);
// 生成随机数并输出
int number = dis(gen);
return task_list[number];
}
void DoingWork(const std::string work_name)
{
//选择一个管道进行工作
Channel one = ChooseChannel();
//创建子进程
pid_t id = fork();
if(id == 0)
{
if(access(one.fifo_path.c_str(),F_OK) == -1) //判断管道是否存在,不存在则退出,存在则打开
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);
}
else
{
int rfd = open(one.fifo_path.c_str(),O_RDONLY);
if(rfd < 0)
{
perror("read fail");
exit(EXIT_FAILURE);
}
else
{
//子进程从管道当中读数据
char buffer[1024];
ssize_t number = read(rfd,buffer,sizeof(buffer));
if(number > 0)
{
buffer[number] = '\0';
}
//读取之后执行任务
Work(buffer);
//任务结束后关闭读端
close(rfd);
}
}
}
else if(id > 0) // 父进程
{
//以写方式打开
if(access(one.fifo_path.c_str(),F_OK) == -1)
{
std::cout<<"channel has be deleted"<<std::endl;
exit(EXIT_SUCCESS);;
}
else
{
int wfd = open(one.fifo_path.c_str(),O_WRONLY);
if(wfd < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
else
{
//往管道里面写入
//我们用一个vector存放string来模拟任务的场景
ssize_t number = write(wfd,work_name.c_str(),strlen(work_name.c_str()));
if(number < 0)
{
perror("write fail");
exit(EXIT_FAILURE);
}
//关闭读端
close(wfd);
//回收子进程
waitpid(id,nullptr,0);
}
}
}
else
{
perror("fork fail");
exit(EXIT_FAILURE);
}
}
private:
std::vector<Channel> channels;
};
#include"ProcessPool.hpp"
int main()
{
srand(time(0));
//std::vector<Channel> channels;
//创建管道
//CreateChannels(&channels);
MainProcess mainProcess;
mainProcess.CreateChannels();
//进行交互
for(int i = 0; i < 10; i++)
{
std::string work = mainProcess.SeleteWork();
mainProcess.DoingWork(work);
}
//删除管道
mainProcess.DeletePipes();
}
如果大家还想复杂的话,可以把work函数在实现的复杂一点,使之更加智能。