这次,我们来写一个线程池的非常简陋代码,帮助我们理解
我们之前也讲到过:关于C++的池化技术。
进程池是预先创建一组子进程,统一管理任务分配与资源回收的并发模型
什么是进程池?
进程池是预先创建一组空闲进程,统一管理任务队列,避免频繁创建/销毁线程开销的并发编程模型
思路是:利用父进程管理所有的子进程,而让子进程去完成某种任务!
我们通过匿名管道来实现:
父进程传输信息给子进程,子进程根据父进程传输的信息来执行不同的任务!
简单来说就是:父进程发出指令,由子进程去执行
为什么进程池能够提高效率?
因为OS不相信用户,因此,我们的执行任务的代码中,必定会由大量的系统调用来构成,而上几篇我们讲到,它调用是有成本的!
现在我们采用:预先创建进程,要用时直接拿,而不是需要用到是再去创建进程。同时,在执行完一个任务的子进程还可以继续去执行另外的任务,从而用重复利用的方法提高了资源效率。
我们知道:管理好事物的本质就是:"先描述,在组织!"
创建对象结构体:
class channel
{
public:
channel(int comfd, pid_t slaverid, std::string processname)
: _comdfd(comfd), _slaverid(slaverid), _processname(processname)
{
}
// private:
public:
int _comdfd; // 发送任务的文件描述符
pid_t _slaverid; // 子进程的pid
std::string _processname; // 子进程的名字,方便打日志
};
我们操作流程主要有:
初始化
控制进程
关闭进程
int main()
{
//模拟执行任务
LoadTask(&tasks);
//种下随机数种子
srand(time(nullptr));
// 再组织
std::vector<channel> channels;
// 初始化,创建多个进程,建立框架!
InitSliver(&channels);
// Debug(channels);
// std::cout<<channels[0]._comdfd<<":"<<channels[0]._slaverid<<":"<<channels[0]._processname<<std::endl;
//控制进程
ctrlSlaver(channels);
QuitSlaver(channels);
return 0;
}
模拟执行任务:
#pragma once
#include<iostream>
#include<vector>
//自定义函数指针
typedef void(*task_t)();
void Task1()
{
std::cout<<"检查单词是否有问题"<<std::endl;
}
void Task2()
{
std::cout<<"检查是否要更新版本"<<std::endl;
}
void Task3()
{
std::cout<<"查看下载进度"<<std::endl;
}
void Task4()
{
std::cout<<"查看用户是否需要登录"<<std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{
tasks->push_back(Task1);
tasks->push_back(Task2);
tasks->push_back(Task3);
tasks->push_back(Task4);
进程执行任务
void sliver()
{
while(true)
{
int cmdcode=0;
//读取到cmdcode中
int n=read(0,&cmdcode,sizeof(int));
if(n==sizeof(int))
{
std::cout<<"slaver get a command: "<<getpid()<<" comdcode:"<<cmdcode<<std::endl;
if(cmdcode>=0 &&cmdcode<tasks.size())
{
tasks[cmdcode]();
}
}
if(n==0)
break;
}
}
初始化部分
//输出型参数 *
//输入型参数const &
//输入输出型参数 &
void InitSliver(std::vector<channel>* channels)
{
//确保每一个子进程都只有一个写端
std::vector<int>oldfd;
for (int i = 0; i < 10; i++)
{
int pipefd[2];
// 建立管道
int n = pipe(pipefd);
if (n < 0)
{
perror("pipe");
return;
}
// 创建父子进程
pid_t id = fork();
if (id < 0)
{
perror("fork");
return;
}
else if (id == 0) // child
{
for(auto&fd:oldfd)
close(fd);
close(pipefd[1]); // 关闭一端
dup2(pipefd[0], 0);
close(pipefd[0]);
sliver();
std::cout<<"process: "<<getpid()<<" quit"<<std::endl;
exit(0);
}
// father
close(pipefd[0]); // 关闭一端
std::string processname = "process" + std::to_string(i);
channels->push_back({pipefd[1], id, processname});
oldfd.push_back(pipefd[1]);
// sleep(1);
}
}
简洁菜单:
void Menu()
{
std::cout << "####################################################################" << std::endl;
std::cout << "####################################################################" << std::endl;
std::cout << "#######1.检查单词是否有问题 2.检查是否要更新版本###################" << std::endl;
std::cout << "#######3. 查看下载进度 4.查看用户是否需要登录#######################" << std::endl;
std::cout << "##########0.退出####################################################" << std::endl;
std::cout << "####################################################################" << std::endl;
}
控制进程模块:
void ctrlSlaver(const std::vector<channel>& channels)
{
int count=0;
while(true)
{
int select=0;
Menu();
std::cout<<"Please Enter# "<<std::endl;
std::cin>>select;
if(select<=0 ||select>5)
break;
//选择任务
// int cmdcode=rand()%tasks.size();
int cmdcode=select-1;
//选择进程
int processpos=rand()%tasks.size();
std::cout<<"father say:"<<"cmdcode:"<<cmdcode<<" already send to "<<channels[processpos]._slaverid<<
" process name: "<<channels[processpos]._processname<<std::endl;
//发送任务
write(channels[processpos]._comdfd,&cmdcode,sizeof(cmdcode));
count++;
if(count>=5)
break;
sleep(1);
}
}
关闭进程
void QuitSlaver(const std::vector<channel>& channels)
{
for(const auto e:channels)
{
close(e._comdfd);
waitpid(e._slaverid,nullptr,0);
}
}
注意:关闭进程这里不能写成这样:
for(const auto &c : channels) close(c._cmdfd);
for(const auto &c : channels) waitpid(c._slaverid, nullptr, 0);