Linux——进程池(管道)

经过了管道的介绍之后,我们可以实现了进程间通信,现在我就来简单介
绍一下管道的应用场景——进程池。

1. 引入

在我们的编码过程中,不乏会听到,内存池,进程池,空间配置器等等名词,这些是用来干嘛的呢?
我们在自己写一个顺序表等容器的时候,我们的容器的容量的扩容不是需要一个我们就开一个,而是以整数倍,开辟内存。这样做的好处是,我们在使用的顺序表的时候可以一定程度上减少扩容的消耗(数据迁移,函数调用)。提高我们代码的效率。这样我们的资源就像一个池子一样,用之即取。这也是池化技术的思想。而我们的进程池也是一样,有的时候我们的主进程再进行一些任务的时候,需要创建一个新进程来执行这些任务,但是如果是在执行任务的时候在创建进程的话,会降低代码的整体效率,所以对于这样的场景,我们就可以提前创建好所需要的进程来供主进程使用。这也是利用了池化的思想。
所以池化的思想也就是指预先分配一定数量的资源,然后在需要时动态地从资源池中获取资源,使用完毕后再将资源归还到资源池中,以提高资源利用率和系统性能。
而我们进程池要实现首先就需要实现进程间通信的功能。如果没有这一点功能,我们的进程如何分配给子进程任务。

2. 进程池的创建

a. 信道以及子进程的创建

所以我们现在来编写一首简单的进程池:
上面也说了,要实现进程池首先就得有进程间通信的功能,所以我们这里选择使用匿名管道,并且假设我们需要五个子进程来执行任务:
首先我们需要能够创建出信道和子进程:

#include <iostream>
#include <cstring>
#include <unistd.h>

using namespace std;

int main()
{

    // 创建与子进程通信的管道(信道)
    int pipefd[2];
    int piperet = pipe(pipefd);
    if(piperet < 0)
    {
        cout << "erron: " << errno << " error: " << strerror(errno) << endl;
        return 1;
    }

    // 创建子进程,建立信道
    pid_t id = fork();
    if(id < 0)
    {
        cout << "erron: " << errno << " error: " << strerror(errno) << endl;
        return 1;
    }
    else if(id == 0)
    {
        // 子进程
        close(pipefd[1]); // 关闭写端

        exit(0);
    }
    // 父进程
    close(pipefd[0]); // 关闭读端

    return 0;
}

在此基础上我们添加个循环不就可以,创建多个子进程了吗:
在这里插入图片描述
但是现在就有一个问题,我们所使用的pipefd数组在循环中,出了作用域它就不存在了,那么我们还怎么给子进程分配任务呢?其实我们使用一个数组存储相关数据就可以了,那存储什么呢?只存一个文件描述符可以吗?我们现在是一个主进程,当我们子进程们分配任务时,我们需不需要知道它是哪个进程,fd是让操作系统来认识的,我们是程序员,我们可以对子进程也就是信道命名啊,这样的特定的进程做特定的事不久也可以实现了嘛。既然它是子进程的话,那是不是应该再纪录它的pid啊。所以我们的存储对象应该是一个结构体,它其中有父进程写端对应的fd,信道名,子进程的pid:
在这里插入图片描述
在这里插入图片描述
插入一个数组中,供父进程方便分配。

b. 执行任务

现在我们可以,让子进程执行任务了,我们的假设是通过某种映射关系,能够让子进程知道进行哪些任务,而这种映射关系的key是固定四字节大小的,执行任务也是一直需要循环的:
在这里插入图片描述

c. 任务的构建

现在我们有了任务的执行,我们可以创建几个形式上的任务:
在这里插入图片描述
我们说了会以映射的方式来让子进程明确子进程执行的是哪个任务,在这里我们要让一个函数和数字有了某种映射关系,在这里我们使用这种方式:
在这里插入图片描述

这样我们就建立起了任务与整数的关系。
还需要一个接口,并且我们设计分配任务目前是随机的,所以还需要一个随机数种子:
在这里插入图片描述

d. 任务的选择与信道的选择

这里我们任务的选择使用随机数选择,信道的选择使用循环的方式来选择。实际场景中任务的选择和信道的选择肯定是根据每个进程的任务完成状态来实行任务的分配的,这里我们就简单一点:
在这里插入图片描述
在这里插入图片描述
然后稍稍优化一下:
在这里插入图片描述
在这里插入图片描述
我们就可以看到这样的效果:
在这里插入图片描述

e. 子进程的回收

我们知道当对于管道通信而言,写端关闭,那么读端的read函数会一直返回0。利用这一点,我们可以实现对子进程的回收:
在这里插入图片描述
我们这时候对代码改造一下,让它执行到一定次数后结束分配任务:
在这里插入图片描述
然后我们,执行代码测试一下:
在这里插入图片描述
我们发现它卡住了,我们并没有使用休眠函数,其实这是有原因的,我们再来看看这么一段回收子进程的代码:
在这里插入图片描述
再来执行一次:
在这里插入图片描述
回收成功了,我来解释一下其中的原因:
我们的主进程创建好第一个子进程的时候应该是这样:
在这里插入图片描述
这时候,我们再创建第二个进程:
在这里插入图片描述
可以看到第二个子进程的文件描述符表中有一个指向了第一个信道。而我们回收进程的时候,我们第一个回收的是第一个进程,我们关闭了主进程的4号文件,但是仍有文件指针指向信道一号以写的方式,所以这个时候我们,这时候要回收第一个子进程,第一个子进程就没有退出,所以会一直阻塞在回收第一个子进程的代码上,所以会卡了。而处理这个的办法也不难:
第一中方式就是,先关闭所有写端,那所有进程都会退出,这个时候我们再回收进程:
在这里插入图片描述
还有一种方式就是,倒着回收进程,它不能回收的原因不就是后续创建进程时,后面的进程中的文件描述符表中会指向前面的信道嘛:
在这里插入图片描述

在这里插入图片描述
还有一种方式就是在创建进程的时候,我们可以记录下主进程的写端fd,然后再后续创建子进程的时候给他关闭就可以了,这样我们就可以直接回收子进程了:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
以上就是一个简略版的进程池,我为了阐述方便没有过度的进行封装,有兴趣的小伙伴可以自行封装一手。全部代码如下:

#include <iostream>
#include <functional>
#include <ctime>
#include <cassert>
#include <string>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

class channal
{
public:
    channal(int fd, pid_t id, int num)
        : _fd(fd), _id(id), _name("channal-" + to_string(num))
    {
    }

    int _fd;
    pid_t _id;
    string _name;
};

void download()
{
    cout << "我是一个下载任务,我的pid是:" << getpid() << endl;
}

void printlog()
{
    cout << "我是一个打印日志任务,我的pid是:" << getpid() << endl;
}

void compressedfile()
{
    cout << "我是一个压缩文件任务,我的pid是:" << getpid() << endl;
}

using task_t = function<void()>;

class task
{
public:
    task()
    {
        _messions.push_back(download);
        _messions.push_back(printlog);
        _messions.push_back(compressedfile);

        srand((unsigned)time(nullptr));
    }

    task_t gettask(int num)
    {
        assert(num >= 0 && num < _messions.size());

        return _messions[num];
    }

    int gettasksize()
    {
        return _messions.size();
    }

    string gettaskname(int num)
    {
        assert(num >= 0 && num < _messions.size());

        switch (num)
        {
        case _download:
            return "_download";
            break;
        case _printlog:
            return "_printlog";
            break;
        case _compressedfile:
            return "_compressedfile";
            break;

        default:
            return "";
            break;
        }
    }

private:
    static const int _download = 0;
    static const int _printlog = 1;
    static const int _compressedfile = 2;

    vector<task_t> _messions;
};

task task_list;

int main()
{
    vector<channal> channals;
    vector<int> tmp;
    for (int i = 0; i < 5; i++)
    {

        // 创建与子进程通信的管道(信道)
        int pipefd[2];
        int piperet = pipe(pipefd);
        if (piperet < 0)
        {
            cout << "erron: " << errno << " error: " << strerror(errno) << endl;
            return 1;
        }

        // 创建子进程,建立信道
        pid_t id = fork();
        if (id < 0)
        {
            cout << "erron: " << errno << " error: " << strerror(errno) << endl;
            return 1;
        }
        else if (id == 0)
        {
            // 子进程
            if (!tmp.empty())
            {
                for (int i = 0; i < tmp.size(); i++)
                {
                    close(tmp[i]);
                }
            }

            close(pipefd[1]); // 关闭写端
            while (true)
            {
                int mession = 0;
                ssize_t n = read(pipefd[0], &mession, sizeof(mession));
                if (n < 0)
                {
                    cout << "erron: " << errno << " error: " << strerror(errno) << endl;
                    exit(1);
                }
                else if (n != 4)
                    break;
                else
                {
                    // 执行任务
                    task_list.gettask(mession)();
                }
            }

            exit(0);
        }
        // 父进程
        close(pipefd[0]); // 关闭读端
        channals.push_back({pipefd[1], id, i});
        tmp.push_back(pipefd[1]);
    }

    // 父进程就可以开始给子进程分配任务了
    int pos = 0;
    int x = 0;
    while (true)
    {
        pos %= channals.size();
        int mession = rand() % task_list.gettasksize();
        ssize_t n = write(channals[pos]._fd, &mession, sizeof(mession));
        cout << "分配给信道:" << channals[pos]._name << "任务:" << task_list.gettaskname(mession) << "它的pid是:" << channals[pos]._id << endl;
        if (n < 0)
        {
            cout << "erron: " << errno << " error: " << strerror(errno) << endl;
            return 1;
        }
        sleep(1);
        pos++;
        x++;
        if (x == 10)
        {
            break;
        }
    }

    // // 回收子进程
    // for (int i = 0; i < channals.size(); i++)
    // {
    //     close(channals[i]._fd);
    // }

    // for (int i = 0; i < channals.size(); i++)
    // {
    //     pid_t retpid = waitpid(channals[i]._id, nullptr, 0);
    //     if (retpid == channals[i]._id)
    //     {
    //         cout << "回收子进程成功:" << retpid << endl;
    //     }
    // }
    // for (int i = channals.size() - 1; i >= 0; i--)
    // {
    //     close(channals[i]._fd);
    //     pid_t retpid = waitpid(channals[i]._id, nullptr, 0);
    //     if (retpid == channals[i]._id)
    //     {
    //         cout << "回收子进程成功:" << retpid << endl;
    //     }
    // }

    for (int i = 0; i < channals.size(); i++)
    {
        close(channals[i]._fd);
        pid_t retpid = waitpid(channals[i]._id, nullptr, 0);
        if (retpid == channals[i]._id)
        {
            cout << "回收子进程成功:" << retpid << endl;
        }
    }

    return 0;
}

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

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

相关文章

NLP_神经概率语言模型(NPLM)

文章目录 NPLM的起源NPLM的实现1.构建实验语料库2.生成NPLM训练数据3.定义NPLM4.实例化NPLM5.训练NPLM6.用NPLM预测新词 NPLM小结 NPLM的起源 在NPLM之前&#xff0c;传统的语言模型主要依赖于最基本的N-Gram技术&#xff0c;通过统计词汇的共现频率来计算词汇组合的概率。然而…

【Linux】SystemV IPC

进程间通信 一、SystemV 共享内存1. 共享内存原理2. 系统调用接口&#xff08;1&#xff09;创建共享内存&#xff08;2&#xff09;形成 key&#xff08;3&#xff09;测试接口&#xff08;4&#xff09;关联进程&#xff08;5&#xff09;取消关联&#xff08;6&#xff09;释…

5周年狂欢,WeTrade众汇积分商城又送车啦!

各位投资者&#xff1a;新年好啊&#xff01; WeTrade众汇承诺积分商城所有礼品&#xff0c;不论价值大小&#xff0c;送出均为真实有效&#xff0c;不做虚假宣传。 WeTrade众汇继2018年9月28日送出特斯拉Model X后&#xff0c;又一次迎来了第二位在积分商城兑换豪车的客户! …

(全网最全)微型计算机原理与接口技术第六版课后习题答案-周荷琴,冯焕清-第8章中断和可编程中断控制器8259A-中国科学技术大学出版社

含有“AI:”开头的题目的答案是问chat的&#xff0c;看个乐就行&#xff0c;不一定正确 1。 什么叫中断&#xff1f;中断的主要功能是什么&#xff1f; 答:当CPU正在处某件事情的时候,外部发生的某一事件请求CPU迅速去处理,于是,CPU暂时中止当前工作,转去处理所发生的事件,中…

《MySQL 简易速速上手小册》第4章:数据安全性管理(2024 最新版)

文章目录 4.1 用户认证和权限控制4.1.1 基础知识4.1.2 重点案例&#xff1a;使用 Python 管理 MySQL 用户权限4.1.3 拓展案例 4.2 防止 SQL 注入和其他安全威胁4.2.1 基础知识4.2.2 重点案例&#xff1a;使用 Python 和 MySQL 进行安全的数据查询4.2.3 拓展案例 4.3 数据加密和…

算法练习-二叉搜索树中的搜索(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;二叉树 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…

CUDA简介

CPUGPU异构计算 GPU计算并不是指单独的GPU计算&#xff0c;而是指CPUGPU的异构计算。一块单独的GPU是无法独立的完成所有计算任务的&#xff0c;它必须在CPU的调度下才能完成特定的任务。CPU更适合进行逻辑复杂低并行的程序&#xff0c;GPU更适合逻辑简单高并行的任务。这主要…

问题:必须坚持以中国式现代化推进中华民族伟大复兴,既不走封闭僵化的老路,也不走 #媒体#知识分享

问题&#xff1a;必须坚持以中国式现代化推进中华民族伟大复兴&#xff0c;既不走封闭僵化的老路&#xff0c;也不走 A、中国特色社会主义道路 B、改革开放之路 C、改旗易帜的邪路 D、中国式现代化之路 参考答案如图所示

HarmonyOS 鸿蒙 ArkTS 双色旋转动画效果

下载地址&#xff1a; https://download.csdn.net/download/weixin_54226053/88818859 也可以点击顶部的资源下载

【Git】Windows下通过Docker安装GitLab

私有仓库 前言基本思路拉取镜像创建挂载目录创建容器容器启动成功登录仓库设置中文更改密码人员审核配置邮箱 前言 由于某云存在人数限制&#xff0c;这个其实很好理解&#xff0c;毕竟使用的是云服务器&#xff0c;人家也是要交钱的。把代码完全放在别人的服务器上面&#xf…

Qt网络编程-ZMQ的使用

不同主机或者相同主机中不同进程之间可以借助网络通信相互进行数据交互&#xff0c;网络通信实现了进程之间的通信。比如两个进程之间需要借助UDP进行单播通信&#xff0c;则双方需要知道对方的IP和端口&#xff0c;假设两者不在同一主机中&#xff0c;如下示意图&#xff1a; …

C#,十进制展开数(Decimal Expansion Number)的算法与源代码

1 十进制展开数 十进制展开数&#xff08;Decimal Expansion Number&#xff09;的计算公式&#xff1a; DEN n^3 - n - 1 The decimal expansion of a number is its representation in base -10 (i.e., in the decimal system). In this system, each "decimal place…

戴上HUAWEI WATCH GT 4,解锁龙年新玩法

春节将至&#xff0c;华为WATCH GT 4作为一款颜值和实力并存的手表&#xff0c;能为节日增添了不少趣味和便利。无论你是钟情于龙年表盘或定制属于自己的表盘&#xff0c;还是过年用来抢红包或远程操控手机拍全家福等等&#xff0c;它都能成为你的“玩伴”。接下来&#xff0c;…

C++后端开发之Sylar学习三:VSCode连接Ubuntu配置Gitee

C后端开发之Sylar学习三&#xff1a;VSCode连接Ubuntu配置Gitee 为了记录学习的过程&#xff0c;学习Sylar时写的代码统一提交到Gitee仓库中。 Ubuntu配置Gitee 安装git sudo apt-get install -y git配置用户名和邮箱 git config --global user.name 用户名 …

产品效果图为何要用渲染100农场?渲染100邀请码1a12

产品效果图很重要&#xff0c;它能帮助设计人员和消费者理解产品特点&#xff0c;是不可或缺的一步。产品效果图渲染耗时耗力&#xff0c;不仅慢而且容易出错&#xff0c;在这种情况下&#xff0c;使用渲染农场就成了必备选择&#xff0c;以目前国内最好的渲染农场渲染100为例&…

【芯片设计- RTL 数字逻辑设计入门 番外篇 9 -- SOC 中PL端与PS端详细介绍】

文章目录 Programmable Logic and Processing SystemPL&#xff08;Programmable Logic&#xff09;特点PS和PL之间的协同设计和开发工具 Programmable Logic and Processing System 在系统级芯片&#xff08;SoC&#xff09;的上下文中&#xff0c;“PL” 通常指的是可编程逻…

JavaScript基础第六天

JavaScript 基础第六天 今天我们学习数组的遍历&#xff0c;以及数组的其他用法。 1. 数组遍历 1.1. 古老方法 可以使用 for 循环进行遍历。 let arr ["a", "b", "d", "g"]; for (let i 0; i < arr.length; i) {console.log…

HiveSQL——用户中两人一定认识的组合数

注&#xff1a;参考文章&#xff1a; SQL之用户中两人一定认识的组合数--HQL面试题36【快手数仓面试题】_sql面试题-快手-CSDN博客文章浏览阅读1.2k次&#xff0c;点赞3次&#xff0c;收藏12次。目录0 需求分析1 数据准备2 数据分析3 小结0 需求分析设表名&#xff1a;table0现…

Jupyter Notebook如何在E盘打开

Jupyter Notebook如何在E盘打开 方法1&#xff1a;方法2&#xff1a; 首先打开Anaconda Powershell Prompt, 可以看到默认是C盘。 可以对应着自己的界面输入&#xff1a; 方法1&#xff1a; (base) PS C:\Users\bella> E: (base) PS E:\> jupyter notebook方法2&#x…

分析 丨ToF传感器的XR应用和主要厂商

苹果MR头显Vision Pro被业界关注&#xff0c;另有消息称华为在2024年规划2款产品&#xff0c;一个是与Vision Pro、Quest和PICO方案类似的MR头显&#xff0c;预计2024年Q3或者Q4发布&#xff1b;另一个是与魅族MYVU衍射光波导AR眼镜类似的产品&#xff0c;发布时间晚于MR头显。…