Linux——进程池

Linux——进程池

  • 池化技术
  • 进程池
  • 信道
  • 模拟任务
  • 进程退出
  • 一个bug

今天我们来学习一下管道的应用——进程池。如果有没看过上一篇管道的小伙伴可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/136371517

池化技术

我们首先要了解一下池化技术

池化技术(Pooling)在计算机技术中是一种常见的设计模型,主要用于优化资源使用和提高性能。其核心理念是提前保存并维护大量资源在一个特定的“池子”中,以备不时之需以及重复使用。这样可以显著减少资源创建和销毁的开销,从而提高系统的响应速度和效率。
池化技术的主类型
线程池:线程池类似于操作系统中的缓冲区概念。它预先创建并管理一定数量的线程,这些线程在初始状态下都处于睡眠状态。当有新任务或请求到来时,线程池会唤醒一个睡眠线程来处理该任务,处理完成后线程再次进入睡眠状态。这样可以避免频繁地创建和销毁线程,从而提高性能。
内存池:内存池用于管理内存资源。由于分配和释放内存涉及到系统调用,这会导致程序从用户态切换到内核态,是一个相对耗时的操作。内存池通过预先分配一定大小的内存块并统一管理,可以显著减少内存分配和释放的开销。
数据库连接池:数据库连接池用于管理数据库连接。由于创建和关闭数据库连接是一个相对耗时的操作,数据库连接池通过预先创建并管理一定数量的数据库连接,可以显著提高数据库访问的性能。
对象池:对象池是一种常见的对象缓存手段。它预先创建并管理一定数量的对象,当需要使用对象时,直接从对象池中取出而不是重新创建。这样可以减少对象创建和销毁的开销,提高对象的访问性能。
池化技术的优点主要包括:
提高资源使用效率:通过复用已有的资源,减少了频繁创建和销毁资源的开销。
降低系统资源消耗:通过统一管理资源,可以更好地控制资源的使用,避免资源的浪费。
提高系统性能:通过减少资源创建和销毁的开销,以及优化资源的使用,可以提高系统的响应速度和性能。
然而,池化技术也需要注意一些问题,如资源的管理和维护、资源的复用策略、资源的生命周期管理等。此外,不同的池化技术需要根据具体的应用场景和需求来选择和使用。

简单一点来说,就是“未雨绸缪”,计算的池化技术就是当处理某些事务的时候,先把对应的资源先准备好,到时候可以直接上手处理事务,省下了开销资源的时间。

进程池

我们今天要做的,是写一个进程池,就是提前先创建好一批进程,等到有任务来的时候,直接可以处理任务:

在这里插入图片描述
我们首先把架子搭好:

#include<iostream>
#include<unistd.h>
const int num = 5;
using namespace std;
#include<cassert>int main()
{//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1])}//父进程//关闭读端close(pipefd[0]);}
}

信道

现在我们创建好了进程,但是有个问题,我们并不知道什么时候该往哪个进程发配任务,现在我们的主进程跟我们创建的进程没有任何的关系,这个时候,我们就要用信道

在这里插入图片描述
通过信道(本质上也是一种管道),我们主进程就知道该往哪个进程发配任务了。我们可以创建一个类对它进行管理:

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<vector>
const int num = 5;
static int channel_number = 1; //信道起始数量
using namespace std;
#include<cassert>class channel
{
public:channel(int fd,pid_t id):ctrlfd(fd),workid(id){name = "channel->" + to_string(channel_number++);}int ctrlfd; //读写端的fdpid_t workid; //子进程idstring name; //管道名字
};int main()
{vector<channel> channels; //信道//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1])exit(0);}//父进程//关闭读端close(pipefd[0]);channels.push_back(channel(pipefd[1],id)); //往信道写入}
}

然后我们把创建信道的过程抽象出来形成一个函数:

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<vector>
const int num = 5;
static int channel_number = 1; //信道起始数量
using namespace std;
#include<cassert>class channel
{
public:channel(int fd,pid_t id):ctrlfd(fd),workid(id){name = "channel->" + to_string(channel_number++);}int ctrlfd; //读写端的fdpid_t workid; //子进程idstring name; //管道名字
};void CreateChannel( vector<channel> *channels)
{//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1])exit(0);}//父进程//关闭读端close(pipefd[0]);channels->push_back(channel(pipefd[1],id)); //往信道写入}
}int main()
{vector<channel> channels; //信道//创建信道CreateChannel(&channels);}

这里我们规范一下传参方式:

传参形式:

  1. 输入参数:const &
  2. 输出参数:*
  3. 输入输出参数:&

我们创建一个函数来表示子进程的工作:

void Work()
{while(true){cout<< "I am running "<< getpid() << endl;sleep(1);}
}
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<vector>
const int num = 5;
static int channel_number = 1; //信道起始数量
using namespace std;
#include<cassert>class channel
{
public:channel(int fd,pid_t id):ctrlfd(fd),workid(id){name = "channel->" + to_string(channel_number++);}int ctrlfd; //读写端的fdpid_t workid; //子进程idstring name; //管道名字
};void Work()
{while(true){cout<< "I am running "<< getpid() << endl;sleep(1);}
}void CreateChannel( vector<channel> *channels) //创建信道
{//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1]);//子进程要完成的工作Work();exit(0);}//父进程//关闭读端close(pipefd[0]);channels->push_back(channel(pipefd[1],id)); //往信道写入}
}//测试
void PrintChannel(const vector<channel> &channels) //输入型参数
{for(auto e: channels){cout<<e.name<<", "<<e.ctrlfd<<", "<<e.workid<<endl;}
}int main()
{vector<channel> channels; //信道//创建信道CreateChannel(&channels);PrintChannel(channels);sleep(10);return 0;}

我们可以运行一下看看:
在这里插入图片描述
此时我们完成了第一步,建立信道。

模拟任务

现在我们建立好了信道,接下来就是接收主进程给我们的任务就可以了,可是子进程如何接收和识别任务呢?我们这里规定:传不同的数字,做不同的任务

首先,我们这里先重定向,从标准输入读取(省略传参):

      if(id == 0) //子进程{//关闭写端close(pipefd[1]);//子进程要完成的工作dup2(pipefd[0],0); //重定向,向标准输入读Work();exit(0);}
void Work()
{while(true){int code = 0; //任务代码int n = read(0,&code,sizeof(code));assert(n == sizeof(code)); //要做的任务}
}

我们可以开一个hpp文件,来模拟我们的任务:

#pragma once#include<iostream>
#include<functional>
#include<vector>
#include <ctime>
#include<unistd.h>typedef std::function<void()> task_t; //管理任务void Download()
{std::cout << "I am a Download"<< " deal with: " << getpid() << std::endl;
}void PrintLog()
{std::cout << "I am a log"<< " deal with: " << getpid() << std::endl;
}void PushVideoStream()
{std::cout << "I am a vdieo"<< " deal with: " << getpid() << std::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;// 任务集合std::vector<task_t> tasks;
public:Init(){tasks.push_back(Download);tasks.push_back(PrintLog);tasks.push_back(PushVideoStream);srand(time(nullptr) ^ getpid());}bool CheckSafe(int code){if (code >= 0 && code < tasks.size())return true;elsereturn false;}void RunTask(int code) //运行任务{return tasks[code]();}int SelectTask() //选择任务{return rand() % tasks.size();}std::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; //创建对象

我们相应文件的变化:

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<vector>
const int num = 5;
static int channel_number = 1; //信道起始数量
using namespace std;
#include<cassert>
#include"Task.hpp"class channel
{
public:channel(int fd,pid_t id):ctrlfd(fd),workid(id){name = "channel->" + to_string(channel_number++);}int ctrlfd; //读写端的fdpid_t workid; //子进程idstring name; //管道名字
};void Work()
{while(true){int code = 0; //任务代码int n = read(0,&code,sizeof(code));assert(n == sizeof(code)); //要做的任务if(!init.CheckSafe(code)) continue;init.RunTask(code);}
}void CreateChannel(vector<channel> *channels)
{//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1]);//子进程要完成的工作dup2(pipefd[0],0); //重定向,向标准输入读Work();exit(0);}//父进程//关闭读端close(pipefd[0]);channels->push_back(channel(pipefd[1],id)); //往信道写入}
}void PrintChannel(const vector<channel> &channels) //输入型参数
{for(auto e: channels){cout<<e.name<<", "<<e.ctrlfd<<", "<<e.workid<<endl;}
}void SendCommand(const std::vector<channel> &channels, bool flag, int num = -1)
{int pos = 0;while (true){// 1. 选择任务int command = init.SelectTask();// 2. 选择信道(进程)const auto &channel = channels[pos++];pos %= channels.size();// debugstd::cout << "send command " << init.ToDesc(command) << "[" << command << "]"<< " in "<< channel.name << " worker is : " << channel.workid << std::endl;// 3. 发送任务write(channel.ctrlfd, &command, sizeof(command));// 4. 判断是否要退出if (!flag){num--;if (num <= 0)break;}sleep(1);}std::cout << "SendCommand done..." << std::endl;
}int main()
{vector<channel> channels; //信道//创建信道CreateChannel(&channels);//PrintChannel(channels);//选择任务,选择信道const bool g_always_loop = true;SendCommand(channels, !g_always_loop, 10);//sleep(10);return 0;}

我们可以运行一下:
在这里插入图片描述

进程退出

其实,我们想让进程退出,就只需要关闭写端就可以了。(此时会读到0,表示已经读到了文件末尾)
所以,我们之前写的代码,要稍微修改一下:
在这里插入图片描述

int main()
{vector<channel> channels; //信道//创建信道CreateChannel(&channels);//PrintChannel(channels);//选择任务,选择信道const bool g_always_loop = true;SendCommand(channels, !g_always_loop, 10);//进程退出,关闭写端for(const auto &channel : channels) //关闭写端{close(channel.ctrlfd);}//sleep(10);return 0;
}

我们可以把这几行代码封装起来(顺便回收子进程):

void ReleaseChannels(vector<channel> channels)
{for (const auto &channel : channels){close(channel.ctrlfd);}//回收子进程for(const auto &channel : channels){pid_t rid = waitpid(channel.workid,nullptr,0);if(rid == channel.workid){cout<<"wait child: "<<channel.workid<<" success"<<endl;}}
}
int main()
{vector<channel> channels; //信道//创建信道CreateChannel(&channels);//PrintChannel(channels);//选择任务,选择信道const bool g_always_loop = true;SendCommand(channels, !g_always_loop, 10);//进程退出,关闭写端ReleaseChannels(channels);//sleep(10);return 0;
}

我们可以运行一下:
在这里插入图片描述

一个bug

其实我们之前写的创建管道的代码有一点bug:

void CreateChannel(vector<channel> *channels)
{//创建多个子进程for(int i = 0; i < num; i++){//创建管道int pipefd[2];int n = pipe(pipefd);//检查是否创建管道成功assert(n == 0);//创建父子进程pid_t id = fork();if(id == 0) //子进程{//关闭写端close(pipefd[1]);//子进程要完成的工作dup2(pipefd[0],0); //重定向,向标准输入读Work();exit(0);}//父进程//关闭读端close(pipefd[0]);channels->push_back(channel(pipefd[1],id)); //往信道写入}
}

现在我们是结束一个进程,回收一个进程,就会有问题:

void ReleaseChannels(vector<channel> channels)
{for (const auto &channel : channels){close(channel.ctrlfd);waitpid(channel.workid,nullptr,0); //关掉一个收一个}// //回收子进程// for(const auto &channel : channels)// {//     pid_t rid = waitpid(channel.workid,nullptr,0);//     if(rid == channel.workid)//     {//         cout<<"wait child: "<<channel.workid<<" success"<<endl;//     }// }
}

在这里插入图片描述
这个时候,进程会卡死。这是为什么呢?

其实,第一次创建子进程时,是没有啥问题的:
在这里插入图片描述
从第二次开始,每次创建的子进程会继承上一个文件描述符表的写端
在这里插入图片描述
这种情况会一直累积,只有最后一个文件只有一个写端。这样会导致我们的信道不会为空,子进程读不到0,不会退出,发生阻塞。

解决方法也很简单,第一种,我们倒着回收:
在这里插入图片描述

第二种,在新的子进程中关闭多余的文件描述符,我们要在创建信道那里做一点小改动:

void CreateChannel(vector<channel> *channels)
{vector<int> tmp; //临时记录,用来记录老的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) //子进程{if(!tmp.empty()){for(auto fd : tmp){close(fd);}PrintFd(tmp);}//关闭写端close(pipefd[1]);//子进程要完成的工作dup2(pipefd[0],0); //重定向,向标准输入读Work();exit(0);}//父进程//关闭读端close(pipefd[0]);channels->push_back(channel(pipefd[1],id)); //往信道写入tmp.push_back(pipefd[1]); //记录老的文件描述符}
}
void PrintFd(const std::vector<int> &fds) //用来打印看看关闭了哪些fd
{cout << getpid() << " close fds: ";for(auto fd : fds){cout << fd << " ";}cout << endl;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/721665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

StarRocks实战——特来电StarRocks应用实践

目录 一、为何引入StarRocks 二、主要应用场景 三、封装或扩展 四、集群监控预警 五、总结规划展望 5.1 使用经验分享 5.2 下一步计划 5.2.1 StarRocks集群自动安装 5.2.2 StarRocks集群高可用架构 原文大佬的这篇StarRocks应用实践有借鉴意义&#xff0c;这里摘抄下来…

Socket网络编程(三)——TCP快速入门

目录 概述TCP连接可靠性1. 三次握手过程2. 四次挥手过程3. 为什么挥手需要四次&#xff1f; 传输可靠性TCP核心APITCP传输初始化配置&建立连接客户端创建Socket建立连接服务端创建ServerSocket监听连接ServerSocket 和 Socket的关系 Socket基本数据类型传输客户端数据传输服…

AI芯片行业深度:发展现状、竞争格局、市场空间及相关公司深度梳理

从广义上讲只要能够运行人工智能算法的芯片都叫作AI芯片&#xff0c;但通常意义上的AI芯片指的是针对人工智能算法做了特殊加速设计的芯片。AI芯片也被称为AI加速器或计算卡&#xff0c;即专门用于处理人工智能应用中的大量计算任务的模块&#xff08;其他非计算任务仍由CPU负责…

ACwing :1221 四平方和 (二分)

*#include <iostream> #include <cstring> #include <algorithm>using namespace std; const int N 5e6 10; int n;struct sum{int s,c,d;bool operator < (const sum &T)const{ // 重载小于符号if(s ! T.s) return s < T.s;if(c ! T.c) …

day11_oop_fianl_satic_多态

今日内容 零、 复习昨日 一、final 二、static 三、多态 四、向上转型&向下转型 五、多态应用 零、 复习昨日 0 类封装步骤 属性私有private提供setget方法 1 继承关键词,继承的好处 extends减少代码重复为多态做准备 2 子类可以使用父类什么 非私有的属性和方法 3 方法重写…

总结:直径测量的发展历程!在线测径仪已成主要方式!

测量在生活、生产和科学探究中扮演着至关重要的角色。从古至今&#xff0c;人们对测量的探索从未停止。而外径作为一种基础的几何尺寸&#xff0c;其测量也经过了多代发展&#xff0c;直到至今被广泛应用到工业生产中的在线测径仪。本文就来介绍一下外径测量的发展历程&#xf…

【pyinstaller打包记录】Linux系统打包可执行文件后,onnxruntime报警告(Init provider bridge failed)

简介 PyInstaller 是一个用于将 Python 程序打包成可执行文件&#xff08;可执行程序&#xff09;的工具。它能够将 Python 代码和其相关的依赖项&#xff08;包括 Python 解释器、依赖的模块、库文件等&#xff09;打包成一个独立的可执行文件&#xff0c;方便在不同环境中运行…

【Sql Server】存储过程的创建和使用事务,常见运用场景,以及目前现状

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

浅析扩散模型与图像生成【应用篇】(五)——SDEdit

5. SDEdit: Guided Image Synthesis and Editing With Stochastic Differential Equations 该文提出一种基于SDE扩散模型的引导图像生成和编辑方法。通过使用者在原图上给出一些引导&#xff0c;比如在图像上涂鸦或者增加一个图块&#xff0c;甚至可以不给定原图&#xff0c;直…

如何从 WordPress 中的静态资源中删除查询字符串

今天有一个客户来问询&#xff0c;hostease主机创建的WordPress站点&#xff0c;在GTMetrix或Pingdom进行网站速度测试&#xff0c;看到有关查询字符串的警告。如果不想看到查询字符串的警告&#xff0c;要如何处理呢?我们测试&#xff0c;可以通过一些处理满足这个需求。我们…

三整数排序问题的解题逻辑

【题目描述】 输入3个整数&#xff0c;从小到大排序后输出。 【样例输入】 20 7 33 【样例输出】 7 20 33 【解析】 本题解法大概有3种&#xff1a; 1、穷举条件法。 此方法先判断a、b、c大小的所有可能&#xff0c;再根据各种可能性输出不同的排序。 思路是先判断a、…

C++17中的类模板参数推导

在C17之前&#xff0c;必须明确指出类模板的所有参数。自从C17起必须指明类模板参数的限制被放宽了。通过使用类模板参数推导(Class Template Argument Deduction(CTAD))&#xff0c;只要编译器能根据初始值推导出所有模板参数&#xff0c;那么就可以不指明参数。 C17中的类模板…

记录一次排查负载均衡不能创建的排查过程

故障现象&#xff0c;某云上&#xff0c;运维同事在创建负载均衡的时候&#xff0c;发现可以创建资源&#xff0c;但是创建完之后&#xff0c;不显示对应的负载均衡。 创建负载均衡时候&#xff0c;按f12发现console有如下报错 后来请后端网络同事排查日志发现&#xff0c;是后…

中科大计网学习记录笔记(十七):拥塞控制原理 | TCP 拥塞控制

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

论文学习—Model-based Adversarial Meta-Reinforcement Learning

Model-based Adversarial Meta-Reinforcement Learning Abstract1. Introduction2. Related work3 Preliminaries基于模型的强化学习&#xff08;MBRL&#xff09;:区别和联系&#xff1a; 4 Model-based Adversarial Meta-Reinforcement Learning4.1 Formulation 4.2 Computin…

LeetCode 每日一题 Day 88 - 94

2673. 使二叉树所有路径值相等的最小代价 给你一个整数 n 表示一棵 满二叉树 里面节点的数目&#xff0c;节点编号从 1 到 n 。根节点编号为 1 &#xff0c;树中每个非叶子节点 i 都有两个孩子&#xff0c;分别是左孩子 2 * i 和右孩子 2 * i 1 。 树中每个节点都有一个值&a…

好书推荐丨细说PyTorch深度学习:理论、算法、模型与编程实现

文章目录 写在前面深度学习推荐图书内容简介作者简介 推荐理由粉丝福利写在最后 写在前面 本期博主给大家推荐一本深度学习的全新正版书籍&#xff0c;感兴趣的小伙伴快来看看吧~ 深度学习 深度学习是机器学习的一个分支&#xff0c;它模仿人脑神经网络的工作原理进行复杂的…

蓝桥杯练习系统(算法训练)ALGO-986 藏匿的刺客

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 强大的kAc建立了强大的帝国&#xff0c;但人民深受其学霸及23文化的压迫&#xff0c;于是勇敢的鹏决心反抗。   kAc帝国防…

linux kernel物理内存概述(二)

目录 物理内存数据结构 设备数物理内存描述 物理内存映射 map_kernel map_mem zone数据结构 zone类型 物理内存数据结构 站在处理器角度&#xff0c;管理物理内存的最小单位是页面。使用page数据结构描述&#xff0c;通常默认大小4kB&#xff0c;采用mem_map[]数组来存…

学习java第一天(下载并配置环境+写第一个java程序)

一.安装 1.下载 直接去官网上选择与你电脑符合的版本下载 官网链接Java Archive Downloads - Java SE 8u211 and later &#xff08;拿我的为例 Windows x64版本&#xff09; ​ 2.然后安装好exe&#xff08;要让自己知道在哪&#xff09; 3.配置环境 大佬链接&#xff1…