Linux——进程池(管道)

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

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;
}

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

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

相关文章

spring boot学习第十二篇:mybatis框架中调用存储过程控制事务性

1、MySQL方面&#xff0c;已经准备好了存储过程&#xff0c;参考&#xff1a;MYSQL存储过程&#xff08;含入参、出参&#xff09;-CSDN博客 2、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"…

zer0pts-2020-memo:由文件偏移处理不正确--引发的堆溢出

启动脚本 #!/bin/sh qemu-system-x86_64 \-m 256M \-kernel ./bzImage \-initrd ./rootfs.cpio \-append "root/dev/ram rw consolettyS0 oopspanic panic1 kaslr quiet" \-cpu kvm64,smep,smap \-monitor /dev/null \-nographic -enable-kvm/ # dmesg | grep page …

电商小程序01需求分析

目录 1 电商用例分析2 功能架构3 原型开发3.1 首页3.2 店铺页面3.3 配货单3.4 配货单有货3.5 我的应用3.6 商品详情3.7 订单确认3.8 收货地址3.9 店铺详情3.10 店铺分类3.11 商品分类 总结 低代码学习的时候最高效的方法就是带着问题去学习&#xff0c;一般可以先从电商小程序开…

507. Perfect Number(完美数)

题目描述 对于一个 正整数&#xff0c;如果它和除了它自身以外的所有 正因子 之和相等&#xff0c;我们称它为 「完美数」。 给定一个 整数 n&#xff0c; 如果是完美数&#xff0c;返回 true&#xff1b;否则返回 false。 问题分析 按照题目要求找出每一个因子&#xff0c…

H12-821_74

74.在某路由器上查看LSP&#xff0c;看到如下结果&#xff1a; A.发送目标地址为3.3.3.3的数据包时&#xff0c;打上标签1026&#xff0c;然后发送。 B.发送目标地址为4.4.4.4的数据包时&#xff0c;不打标签直接发送。 C.当路由器收到标签为1024的数据包&#xff0c;将把标签…

文心一言 VS 讯飞星火 VS chatgpt (196)-- 算法导论14.3 4题

四、用go语言&#xff0c;给定一棵区间树 T 和一个区间 i &#xff0c;请描述如何在 O(min(n&#xff0c;klgn)) 时间内列出 T 中所有与 i 重叠的区间&#xff0c;其中 k 为输出的区间数。(提示:一种简单的方法是做若干次查询&#xff0c;并且在这些查询操作中修改树&#xff0…

Java图形化界面编程—— 基本组件和对话框 笔记

2.5 AWT中常用组件 2.5.1 基本组件 组件名功能ButtonButtonCanvas用于绘图的画布Checkbox复选框组件&#xff08;也可当做单选框组件使用&#xff09;CheckboxGroup选项组&#xff0c;用于将多个Checkbox 组件组合成一组&#xff0c; 一组 Checkbox 组件将只有一个可以 被选中…

[word] word中怎么插入另外一个word文档 #媒体#职场发展

word中怎么插入另外一个word文档 word中怎么插入另外一个word文档&#xff1f;有有些小伙伴在制作文档的时候&#xff0c;可能需要用到多个文档进行配合制作&#xff0c;今天小Q来给大家演示一下&#xff0c;插入Word文档的方法&#xff0c;插入其他类型文档的方法也是一样的。…

notepad++成功安装后默认显示英文怎么设置中文界面?

前几天使用电脑华为管家清理电脑后&#xff0c;发现一直使用的notepad软件变回了英文界面&#xff0c;跟刚成功安装的时候一样&#xff0c;那么应该怎么设置为中文界面呢&#xff1f;具体操作如下&#xff1a; 1、打开notepad软件&#xff0c;点击菜单栏“Settings – Prefere…

Qt简易登录界面

代码&#xff1a; #include "mywidget.h" #include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {ui->setupUi(this);ui->background->setPixmap(QPixmap(":/qt picture/logo.png"))…

C++:二叉搜索树模拟实现(KV模型)

C&#xff1a;二叉搜索树模拟实现&#xff08;KV模型&#xff09; 前言模拟实现KV模型1. 节点封装2、前置工作&#xff08;默认构造、拷贝构造、赋值重载、析构函数等&#xff09;2. 数据插入&#xff08;递归和非递归版本&#xff09;3、数据删除&#xff08;递归和非递归版本…

C# 字体大小的相关问题

设置字体大小无法这么写&#xff0c; button1.Font.Size 20&#xff1b; 这个是只读属性&#xff1b; 把字体大小改为16&#xff0c; button2.Font new Font(button2.Font.Name, 16); 程序运行的时候先看一下窗体和控件的默认字体尺寸&#xff0c;都是9&#xff1b;然后点b…

目标检测 | 卷积神经网络(CNN)详细介绍及其原理详解

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种深度学习模型&#xff0c;主要用于图像识别和计算机视觉任务。它的设计灵感来自于生物学中视觉皮层的工作原理。CNN的核心思想是通…

RedissonClient妙用-分布式布隆过滤器

目录 布隆过滤器介绍 布隆过滤器的落地应用场景 高并发处理 多个过滤器平滑切换 分析总结 布隆过滤器介绍 布隆过滤器&#xff08;Bloom Filter&#xff09;是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是…

Android Studio安装过程遇到SDK无法安装问题解决

首次打开studio遇到该类问题&#xff0c;需要下载SDK文件&#xff0c;后又发现SDK由于是Google源&#xff0c;无法进行正常安装&#xff0c;故转而进行SDK的镜像安装。 一、下载SDK Tools 地址&#xff1a;AndroidDevTools - Android开发工具 Android SDK下载 Android Studio…

华为第二批难题一:基于预训练AI模型的元件库生成

我的理解&#xff1a;华为的这个难道应该是想通过大模型技术&#xff0c;识别元件手册上的图文内容&#xff0c;与现有建库工具结合&#xff0c;有潜力按标准生成各种库模型。 正好&#xff0c;我们正在研究&#xff0c;利用知识图谱技术快速生成装配模型&#xff0c;其中也涉…

微调LLM或使用RAG,开发RAG管道的12个痛点

论文地址&#xff1a;archive.is/bNbZo Pain Point 1: Missing Content 内容缺失 Pain Point 2: Missed the Top Ranked Documents 错过排名靠前的文档 Pain Point 3: Not in Context — Consolidation Strategy Limitations 不在上下文中 — 整合战略的局限性 Pain Point …

DBNet详解及训练ICDAR2015数据集

论文地址&#xff1a;https://arxiv.org/pdf/1911.08947.pdf 开源代码pytorch版本&#xff1a;GitHub - WenmuZhou/DBNet.pytorch: A pytorch re-implementation of Real-time Scene Text Detection with Differentiable Binarization 前言 在这篇论文之前&#xff0c;文字检…

【龙年大礼】| 2023中国开源年度报告!

【中国开源年度报告】由开源社从 2015 年发起&#xff0c;是国内首个结合多个开源社区、高校、媒体、风投、企业与个人&#xff0c;以纯志愿、非营利的理念和开源社区协作的模式&#xff0c;携手共创完成的开源研究报告。后来由于一些因素暂停&#xff0c;在 2018 年重启了这个…

香港倾斜模型3DTiles数据漫游

谷歌地球全香港地区倾斜摄影数据&#xff0c;通过工具转换成3DTiles格式&#xff0c;将这份数据完美加载到三维数字地球Cesium上进行完美呈现&#xff0c;打造香港地区三维倾斜数据覆盖&#xff0c;完美呈现香港城市壮美以及维多利亚港繁荣景象。再由12.5米高分辨率地形数据&am…