Linux多进程(五) 进程池 C++实现

一、进程池的概念

1.1、什么是进程池

进程池是一种并发编程模式,用于管理和重用多个处理任务的进程。它通常用于需要频繁创建和销毁进程的情况,以避免因此产生的开销。

进程池的优点包括:

  1. 减少进程创建销毁的开销:避免频繁创建和销毁进程所带来的系统资源开销。
  2. 提高系统响应速度:由于进程已经初始化并且一直保持在内存中,可以立即分配执行任务,减少了任务等待时间。
  3. 控制资源使用:通过限制进程池中的进程数量,可以控制系统资源的使用情况,避免资源过度消耗。

1.2、管理进程

预先创建一些空闲进程,管理进程会把工作分发到空闲进程来处理,空闲进程处理结束后,通知管理进程。

管理进程需要将任务发给空闲的工作进程,这里就涉及到进程之间的通信,进程间的通信有以下三种方式,我们之前都做了详细的解释

管道:https://blog.csdn.net/weixin_43903639/article/details/138155634?spm=1001.2014.3001.5501

消息队列:https://blog.csdn.net/weixin_43903639/article/details/138155723?spm=1001.2014.3001.5501

共享内存:https://blog.csdn.net/weixin_43903639/article/details/138189200?spm=1001.2014.3001.5501

二、进程池模型

对于一个进程池,我们需要维护一个进程队列,如果进程在忙就等待,如果进程空闲,那么就给空闲进程发任务让进程去处理。

请添加图片描述

三、进程通信

我们这里采用了有名管道的方式。实际上使用匿名管道是一样的。为了方便管理,我们这里创建了一个Fifo的类,通过类将管道视为一个个的对象。

// 权限
#define Mode 0666
// 文件地址
#define Path "./default"

class Fifo
{
public:
    Fifo(string path = Path, int mode = Mode) : _path(path), _mode(mode)
    {
        int return_mkfifo_val = mkfifo(_path.c_str(), _mode);
        if (return_mkfifo_val < 0)
        {
            cout << "mkfifo error:" << errno << " reason :" << strerror(errno) << endl;
            exit(1);
        }
        cout << "mkfifo success" << endl;
    }

    ~Fifo()
    {
        int return_unlink_val = unlink(_path.c_str());
        if (return_unlink_val < 0)
        {
            cout << "unlink error:" << errno << " reason :" << strerror(errno) << endl;
        }
        cout << "unlink namepipe success" << endl;
    }

private:
    string _path;
    int _mode;
};

管道的默认权限是 0666,管道的默认文件地址是 ./default,这样管理的优势是便于管道的创建与销毁。

三、进程对象

我们将一个进程也视为一个对象,那么一个进程就需要以下的元素

  • fd0 : 管道,通过这个管道接收主进程的数据
  • fd1:管道,通过这个管道给主进程发数据
  • pid:子进程的pid
  • isbusy:此子进程是否在忙
class Process
{
public:
    Process(int fd0, int fd1, pid_t process_pid) : _fd0(fd0), _fd1(fd1), _process_pid(process_pid), _isbusy(false) {}

    ~Process() {}

    // 获取父进程要写入的管道
    int get_fd0() { return _fd0; }

    // 获取子进程要写入的管道
    int get_fd1() { return _fd1; }

    // 获取子进程pid
    pid_t get_process_pid() { return _process_pid; }

    // 获取是否忙碌标志位
    bool get_isbusy() { return _isbusy; }

    // 修改标志为
    void set_isbusy(bool flag) { _isbusy = flag; }

private:
    int _fd0;           // 父进程要写入的管道
    int _fd1;           // 父进程要读入的管道
    pid_t _process_pid; // 子进程pid
    bool _isbusy;       // 是否忙碌标志位
};

四、进程池

进程池就是同时管理管道和进程的。其中包含多个进程对象,每个进程对象又要包含两个管道。

  • vector<string> pipe0 主进程写入,子进程读的管道名
  • vector<string> pipe1 子进程写入,主进程读的管道名
  • vector<Fifo *> fifo0 主进程写入,子进程读的管道
  • vector<Fifo *> fifo1 子进程写入,主进程读的管道
  • vector<Process> _processpool 进程池管理的进程
  • int _processnum 进程池管理的进程数量
// 进程池
class ProcessPool
{
public:
    ProcessPool(int processnum) : _processnum(processnum) {}

    ~ProcessPool()
    {
        for (int i = 0; i < _processnum; i++) {
            delete fifo0[i];
            delete fifo1[i];
        }
        // 要释放所有的子进程
        for (int i = 0; i<_processnum; i++) {
            // 通知子进程结束,通知失败的话直接杀死子进程
            if (kill(_processpool[i].get_process_pid(), SIGTERM) != 0) {
                // 杀死子进程
                kill(_processpool[i].get_process_pid(), SIGUSR1);
            }
        }
    }

    // 生成管道的名字
    void makePipeName()
    {
        pipe0.clear();
        pipe1.clear();
        for (int i = 0; i < _processnum; i++)
        {
            string s0;
            s0 += "pipe0_" + to_string(i + 1);
            pipe0.push_back(s0);
            string s1;
            s1 += "pipe1_" + to_string(i + 1);
            pipe1.push_back(s1);
        }
    }

    // 创建进程池,接收一个参数的函数指针
    void CreateProcessPool(work_t work = worker)
    {
        for (int i = 0; i < _processnum; i++)
        {
            // 创建命名管道
            fifo0.push_back(new Fifo(pipe0[i]));
            fifo1.push_back(new Fifo(pipe1[i]));

            int id = fork();
            if (id == 0)
            {
                // 子进程
                int fd0 = open(pipe0[i].c_str(), O_RDONLY);
                int fd1 = open(pipe1[i].c_str(), O_WRONLY);
                work(fd0, fd1);
                exit(0);
            }

            // 父进程打开管道,未发送任务
            int fd0 = open(pipe0[i].c_str(), O_WRONLY);
            int fd1 = open(pipe1[i].c_str(), O_RDONLY | O_NONBLOCK);
            // 设置为非阻塞模式
            fcntl(fd1, F_SETFL, O_NONBLOCK);
            _processpool.push_back({fd0, fd1, id});
        }
    }

    // 找到空闲进程返回进程在_processpool中的序号,没找到返回-1
    int getAChannal()
    {
        for (int i = 0; i < _processnum; i++)
        {
            if (_processpool[i].get_isbusy() == false) {
                _processpool[i].set_isbusy(true);
                return i;
            }
        }
        return -1;
    }

    // 发送任务,任务就是数据
    void SendTask(char *data, int datasize)
    {
        // 随机选中管道
        int luck_process = getAChannal();
        while (luck_process == -1) {
            // 看一下哪些进程是空闲的。每个进程结束后都会发送自己的pid。
            for (int i=0; i<_processnum; i++) {
                pid_t pid = 0;
                ssize_t bytes_read = read(_processpool[i].get_fd1(), &pid, sizeof(pid_t));
                if (bytes_read == -1) continue;
                // 哪个进程返回了数据,就认为Ta结束了。
                else {
                    _processpool[i].set_isbusy(false);
                }
            }
            luck_process = getAChannal();
        }

        // 父进程向管道发送任务,发送任务就是向相应的管道写入数据
        write(_processpool[luck_process].get_fd0(), data, datasize);
    }

    static void worker(int fd0, int fd1)
    {
        char buf[BUFSIZ];
        while (1)
        {
            int read_return_value = read(fd0, buf, BUFSIZ);
            // 处理读取到的数据
            if (read_return_value > 0)
            {
                cout << "my data is : " << buf << " my pid is : " << getpid() << endl;
                // 模拟处理读取到的数据
                sleep(1);
            }
            // 向消息队列传递自己的pid,表示已经完成任务
            pid_t pid = getpid();
            write(fd1, &pid, sizeof(pid_t));
        }
    }

private:
    int _processnum;
    vector<string> pipe0; // 父进程写入,子进程读
    vector<string> pipe1; // 子进程写入,父进程读
    vector<Fifo *> fifo0;
    vector<Fifo *> fifo1;
    vector<Process> _processpool;
};

五、仿真

这里我们的工作函数是默认的工作函数,也就是打印传入的数据。

#include "ProcessPool.h"
#include <iostream>

int main(int argc, char* argv[])
{
    ProcessPool* processpool = new ProcessPool(5);
    processpool->makePipeName();
    processpool->CreateProcessPool();

    char buf[3];

    for (int i=0;i<20;i++) {
        sprintf(buf,"%d",i+100);
        processpool->SendTask(buf,3);
    }

    delete processpool;
    
    return 0;
}

看一下仿真结果

image-20240425194002386

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

笔记:编写程序,分别采用面向对象和 pyplot 快捷函数的方式绘制正弦曲线 和余弦曲线。 提示:使用 sin()或 cos()函数生成正弦值或余弦值。

文章目录 前言一、面向对象和 pyplot 快捷函数的方式是什么&#xff1f;二、编写代码面向对象的方法&#xff1a;使用 pyplot 快捷函数的方法&#xff1a; 总结 前言 本文将探讨如何使用编程语言编写程序&#xff0c;通过两种不同的方法绘制正弦曲线和余弦曲线。我们将分别采用…

备考2024年小学生古诗文大会:做做10道历年真题和知识点(持续)

根据往年的安排&#xff0c;2024年上海市小学生古诗文大会预计还有一个月就将启动。我们继续来随机看10道往年的上海小学生古诗文大会真题&#xff0c;这些题目来自我去重、合并后的1700在线题库&#xff0c;每道题我都提供了参考答案和独家解析。 根据往期的经验&#xff0c;只…

【网络原理】TCP协议的相关机制(确认应答、超时重传)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 文章目…

uniapp制作分页查询功能

效果 代码 标签中 <uni-pagination change"pageChanged" :current"pageIndex" :pageSize"pageSize" :total"pageTotle" class"pagination" /> data中 pageIndex: 1, //分页器页码 pageSize: 10, //分页器每页显示…

第72天:漏洞发现-Web框架中间件联动GobyAfrogXrayAwvsVulmap

案例一&#xff1a;某 APP-Web 扫描-常规&联动-Burp&Awvs&Xray Acunetix 一款商业的 Web 漏洞扫描程序&#xff0c;它可以检查 Web 应用程序中的漏洞&#xff0c;如 SQL 注入、跨站脚本攻击、身份验证页上的弱口令长度等。它拥有一个操作方便的图形用户界 面&#…

基于yolov5实时实例分割

是一个结合了最新技术进展&#xff08;State-of-the-Art, SOTA&#xff09;的实时实例分割项目&#xff0c;基于著名的YOLOv5目标检测架构&#xff0c;并对其进行扩展以实现对图像中每个对象实例的精确像素级分割。以下是该项目的中文介绍&#xff1a; YOLOv5&#xff1a; YOL…

数据结构八:线性表之循环队列的设计

上篇博客&#xff0c;学习了栈&#xff0c;我们可以知道他也是一种线性表&#xff0c;遵从先进后出的原则&#xff0c;在本节&#xff0c;我们进一步学习另一种线性表—队列。就像饭堂里排队打饭的的队伍&#xff0c;作为一种先进先出的线性表&#xff0c;他又有哪些特别之处呢…

实现Spring底层机制(二)

文章目录 阶段2—封装bean定义信息到Map1.代码框架图2.代码实现1.文件目录2.新增注解Scope存储单例或多例信息Scope.java3.修改MonsterService.java指定多例注解4.新增bean定义对象存储bean定义信息BeanDefinition.java5.修改pom.xml增加依赖6.修改容器实现bean定义信息扫描Sun…

C语言|关于C语言变量的作用域、链接、存储期及相关知识(C多文件编程基础)

文章目录 作用域块作用域(block scope)函数作用域(function scope)函数原型作用域(function prototype scope)文件作用域(file scope)翻译单元和文件(作用域&#xff09; 链接(linkage)存储期(Storege Duration)静态存储期(static storage duration)线程存储期(thread storage …

kafka启动报错(kafka.common.InconsistentClusterIdException)

文章目录 前言kafka启动报错(kafka.common.InconsistentClusterIdException)1. 查找日志2. 定位问题/解决 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不…

qt实现方框调整

效果 在四周调整 代码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QWidget>class MainWindow : public QWidget {Q_OBJECT public:explicit MainWindow(QWidget *parent 0);~MainWindow();void paintEvent(QPaintEvent *event);void updateRect();void re…

ZYNQ--PL读写PS端DDR数据

PL 和PS的高效交互是zynq 7000 soc开发的重中之重,我们常常需要将PL端的大量数 据实时送到PS端处理,或者将PS端处理结果实时送到PL端处理,常规我们会想到使用DMA 的方式来进行,但是各种协议非常麻烦,灵活性也比较差,本节课程讲解如何直接通过AXI总 线来读写PS端ddr的数据…

什么是基尼系数

基尼系数是国际上用来综合考察居民内部收入分配差异状况的一个重要分析指标。每个人的收入有多有少&#xff0c;差距大时&#xff0c;基尼系数就高&#xff1b;差距小时&#xff0c;基尼系数就低。 一、基本概念 基尼系数表示在全部居民收入中&#xff0c;用于进行不平均分配…

补充centos7软件包的方式/编译安装源码包软件/企业案例/linux进程管理/企业管理进程系列命令(企业经验)--8820字详谈

cenros7软件包的安装方式 软件包分类安装方式优缺点rpm包软件开发商编译打包&#xff0c;安装简单&#xff0c;快速软件版本可能偏低&#xff0c;安装路径是固定好的源码包自己手动编译安装并且复杂软件爸爸随意选&#xff0c;可以定制安装路径二进制包解压就可以使用不能进行…

什么是AIGC技术

AIGC技术&#xff0c;即人工智能全局优化控制技术&#xff0c;是一种将人工智能与全局优化控制方法相结合的技术。它的主要目标是通过智能化的方法来解决复杂系统的优化问题&#xff0c;提高系统的性能和效率。 AIGC技术的主要目标是利用人工智能算法和技术来实现对系统整体的…

第三篇:Python编程基础:掌握核心语法与开发技巧

Python编程基础&#xff1a;掌握核心语法与开发技巧 1 引言 在这个信息化迅速蔓延的世界中&#xff0c;Python语言如同钥匙一般开启了通往各种可能性的大门。无论你是数据科学家、网络工程师、机器学习专家&#xff0c;还是仅仅对自动化办公感兴趣的办公室人员&#xff0c;Pyt…

【Elasticsearch<二>✈️✈️】基本属性概念与MySQL数据库的不同之处

目录 &#x1f378;前言 &#x1f37b;一、Elasticsearch 基本属性 1.1 ES VS MySQL 1.2 ES 属性概念 1.3 ES 的增删改查 &#x1f37a;二、自动补全场景 2.1 场景举例 2.2 使用数据分词器 2.3 查询的流程 2.4 整个查询流程图 &#x1f379;章末 &#x1f378;前言 上次初步…

Zynq 7000 系列中的Interconnect(互联)简介

PS&#xff08;处理器子系统&#xff09;内部的互联结构包含了多个交换机&#xff0c;用于通过AXI点对点通道连接系统资源。这些通道负责在主机和从机客户端之间进行地址、数据和响应事务通信。该互联结构负责管理多个待处理的事务&#xff0c;并且为Arm CPU设计了低延迟路径&a…

UE4_动画基础_FootIK

角色由于胶囊体的阻挡&#xff0c;双脚与地面平行&#xff0c;不会与斜坡、台阶等贴合&#xff0c;有一条腿会处于悬空状态&#xff0c;通过双骨骼IK节点使一只脚太高&#xff0c;让后胶囊体下降&#xff0c;修正双脚的角度。这就是逆向运动IK的方法。 一、新建第三人称模板游戏…

OpenStack云计算(十四)——综合演练手动部署OpenStack,

本项目的项目实训可以完全参考教材配套讲解的详细步骤实施&#xff0c;总体来说实训工作量较大&#xff0c;可根据需要选做&#xff0c;重点观看配套的微课视频。 项目实训一 【实训题目】 搭建OpenStack云平台基础环境 【实训目的】 掌握OpenStack基础环境的安装和配置方…