【Linux】简易进程池

基于Super-Jacoco的精准测试实践之路 灵魂拷问这个版本的影响范围到底有多大?研发改动了代码,为什么不通知测试?测试用例真的全面覆盖了吗?测试同学的测试覆盖情况该怎么评估?什么是精准测试概念借助技术手段、通过辅助算法对软件测试过... 阅读详情

title: 【Linux】简易进程池
date: 2024-08-02 20:02:48
tags:
cover: https://picbed0521.oss-cn-shanghai.aliyuncs.com/blogpic/202409230843742.png


原文及更多原创文章见我的网站: www.supdriver.top

原理

匿名管道可用于父子进程间的通讯,于是可以有父进程创建多个子进程形成进程池,并通过匿名管道文件向各个子进程派发任务

戳我去管道原理🔗

这里使用C++编写进程池代码,在程序中创建多个进程,并在父进程中使用自定义类channel用于描述和管理子进程,然后在task.hpp中模拟一些任务给主程序随机派发

代码

代码规范

这次对形参有了新的规范,这里用variable代指形参名

  • 输入: const &variable
  • 输出: *variable
  • 输入输出: &variable

准备makefile文件

这里使用g++编译,规定语法标准为C++11

processPool:processPool.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf processPool

准备任务文件

首先要准备前后需要用到的头文件,然后构建模拟任务,并提供加载任务列表的函数

task.hpp

#pragma once

#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
#include <iostream>
#include <string>


typedef void (*task_t)();//定义函数指针

void task1()
{
    std::cout << "PVZ 刷新日志" << std::endl;
}
void task2()
{
    std::cout<< "PVZ 生成阳光"<<std::endl;
}
void task3()
{
    std::cout<<"PVZ 检测更新" <<std::endl;
}
void task4()
{
    std::cout<<"PVZ 使用能量豆"<<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);
}

编写主程序

主程序文件名为processPool.cc,我们按类和接口从上往下编程

描述和组织

我们把头文件准备好,然后定义最大进程数和创建任务列表,接着创建channel类来描述每个进程池中的子进程

#include "task.hpp"
#include <ctime>//获取时间戳
#include <sys/stat.h>
#include <sys/wait.h>


const int processNum = 5;//最大进程数
std::vector<task_t> tasks;//承载任务的映射列表

//先描述
class channel
{
public:
    channel(int cmdfd,pid_t slaverid,std::string& name)
    :_cmdfd(cmdfd),_slaverid(slaverid),_processname(name)
    {}

public:
    int _cmdfd;                    //发送任务的文件描述符
    pid_t _slaverid;               //子进程的id 
    std::string _processname;             //子进程的名字  --方便打印日志
};

slaver函数

创建子进程后,子进程都进入slaver函数等待获取任务

这里统一采用输入重定向,在创建子进程后,进入slaver()前先把标准输入重定向到管道文件,然后从标准输入读取任务码

void slaver()
{
    while(true)
    {
        int cmdcode = 0;
        int n = read(0,&cmdcode,sizeof(int));//读取任务码
        if(n == sizeof(int))//成功获取任务码
        {
            //std::cout<<"child say@ "<<"get cmdcode: "<<cmdcode<<std::endl;//DEBUG
            if(cmdcode >=0 && cmdcode < tasks.size())//输入合法的输入码
            {
                tasks[cmdcode]();
            }
            else //输出错误信息
            {
                std::cout<<"wrong cmdcode: "<<cmdcode 
                    <<" max size: "<<tasks.size()<<std::endl;
            }
        }
    }
}

InnitChannels

该函数用鱼创建子进程,创建子进程,并封装进频道类

void InitChannels(std::vector<channel>* channels)
{
    for(int i = 0;i<processNum;i++)
    {
        int pipefd[2];
        int n = pipe(pipefd);
        assert(!n);
        (void)n;//调用一下n

        pid_t id = fork();
        if(id == 0)//child
        {
            close(pipefd[1]);
            dup2(pipefd[0],0);//子进程输入重定向
            slaver();
            std::cout<<"proccess "<< getpid() << " quit"<<std::endl;
            exit(0);
        } 
        //father
        close(pipefd[0]);
        std::string name = "process-"+std::to_string(i);
        channels->push_back(channel(pipefd[1],id,name));
    }
}

channelDEBUG

这里封装一个用于测试创建频道的DEBUG函数

void Debug(const std::vector<channel> channels)
{
    for(auto &c:channels)
    {
        std::cout<<c._cmdfd << " " << c._slaverid<< " " << c._processname << std::endl;
    }
}

ctrlSlaver

这里封装一个ctrlSlaver用于控制子进程,也就是用于派发任务的函数

void ctrlSlaver(const std::vector<channel> &channels)
{
    for(int i = 0;i<10;i++)//随机派发10个任务
    {
        int cmdcode = rand() % tasks.size();//获取随机任务码
        int select_num = rand()%channels.size();//获取选择码
        std::cout<<"father say# "<<"taskcode: "<<cmdcode<<" send to "<<channels[select_num]._processname
            << std::endl;//输出日志
        write(channels[select_num]._cmdfd,&cmdcode,sizeof(cmdcode));//写入任务码,派发任务
        sleep(1);
    }
}

QuitProcess

最后当然要有父进程控制关闭子进程,并回收僵尸进程

因为InitChannels中管道文件的处理比较粗糙,会发生如下图的关系,所以关闭时两步操作一定要放在两个循环中

void QuitProcess(const std::vector<channel>& channels)
{
    //因为创建子进程时管道文件的连接没有进一步处理,关系复杂,所以下面两个循环一定要分开来
    for(const auto &c:channels)close(c._cmdfd);//关闭主进程管道文件的写端,使子进程的read函数读取失败,返回0
    for(const auto &c:channels)waitpid(c._slaverid,nullptr,0);//阻塞等待子进程,回收僵尸进程
}

main函数

经过上文一系列封装,main函数就可以简洁明了地描述子进程的运行过程了

int main()
{
    srand(time(nullptr) ^ getpid()^1023);//种随机数种子
    std::vector<channel> channels;
    //1.初始化
    LoadTask(&tasks);//必须先载入任务再产生子进程,否则子进程看不到任务列表
    InitChannels(&channels);

    //测试函数
    //Debug(channels);

    //2.控制子进程
    ctrlSlaver(channels);

    //3.关闭进程池
    QuitProcess(channels);

    return 0;
}

源文件

戳我去github仓库🔗

银河麒麟系统V10 Server SP1 安装Oracle 19c •oracle-database-preinstall-19c 这个工具会自动配置系统参数,创建 oracle 用户和组。比较方便。•••••银河麒麟高级服务器操作系统V10 - 国产操作系统、银河麒麟、中标麒麟、开放麒麟、星光麒麟——麒麟软件官方网站。 阅读详情

相关推荐

避免bank conflict的使用共享内存的矩阵转置。及其循环展开。转置前后都是行主序,中间共享内存中是列主序

为了避免bank conflict,可以使用padding的方式将数组中的每一行各自占据一个bank,这样每个线程读取不同行的数据时就不会发生bank conflict。padding的方式是在每行数据后面添加一定数量的空数据,使得每行的字节数恰好是bank的整数倍,这样就能保证每行数据各自占据一个bank。具体来说,当一个线程访问共享内存中的一个元素时,GPU会根据该元素在共享内存中的地址计算出其所在的bank,然后将该元素从该bank中读取出来。

weixin_55035144的博客 2144

Linux:简易进程池编写

本文设计了一个基于进程池的任务分配系统,通过管道实现父进程与子进程间的通信。系统预先创建指定数量的子进程和对应管道,父进程通过轮询方式均衡分配任务(如打印、下载等)给子进程,实现负载均衡。关键实现包括:1) 封装Channel类管理管道和子进程;2) 初始化时关闭冗余管道端避免资源泄漏;3) 采用轮询算法分配任务;4) 任务完成后正确关闭管道和回收子进程。该系统有效减少了频繁创建进程的开销,通过阻塞机制确保空闲子进程不影响整体性能。

分享 C/C++、Linux、网络的学习历程与实操心得~ 不追求一蹴而就,只坚持日积月累。未来还将拓展更多技术领域,愿与所有热爱技术的伙伴一起,在探索中成长,在坚持中进步! 1048

ArcGis布局视图中Excel表格无边框插入与图例符号边框定制技巧

本文详细解析了在ArcGis布局视图中实现Excel表格无边框插入与图例符号边框定制的专业技巧。通过源头净化Excel表格、精准粘贴以及分解图例并单独设置边框属性,有效解决了地图报告中常见的视觉粗糙问题,帮助用户提升专题图与报告的精致度与专业感。

weixin_29292535的博客 305

【Linux系统编程】第三十四弹---使用匿名管道构建简易Linux进程池

引言,进程池的基本概念,管道在进程池中的应用,进程池的实现:master类定义,测试信道,通过channle控制子进程,回收管道和子进程,修复bug,进程池完整代码~~~

小林熬夜学习编程的博客 1547

数字后端实现Innovus教程之手把手教你N种高亮timing path和clock tree方法

数字IC后端设计实现Innovus教程之手把手教你N种高亮timing path和clock tree方法

weixin_37584728的博客 3599

数字IC设计——功耗分析

功耗是数字IC芯片的一个重要指标

知识,既贵,又便宜。 9364

Linux进程间通信----简易进程池实现

我们首先实现几种不同的任务函数,利用回调函数的方式让我们的子进程随机执行任务,为了完成这一步我们利用数组将task(任务)函数用数组管理起来,形成任务清单,任务的发放方式也改为向管道内发送task函数数组的下标,即发放task清单序号,子进程根据从管道内收到的序号去执行清单上对应task。完成任务需要工作人员,在主任务执行的途中源源不断的有新的支线任务来临。进程池是一种多进程编程模式,核心思想是先创建好一定数量的子进程用作当作资源,这些进程可以帮助完成任务并且重复利用,避免频繁的进程的创建和销毁的开销。

nako_sayuri的博客 1210

用Python+OpenCV搞定线激光相机标定:从棋盘格到3D点云的保姆级实战

本文详细介绍了使用Python和OpenCV进行线激光相机标定的全流程实战,从硬件准备到3D点云重建。涵盖相机内参标定、激光平面方程拟合、三维重建等关键技术,提供完整的代码实现和工程优化建议,帮助开发者实现毫米级精度的工业检测和三维测量系统。

weixin_29227425的博客 287

【Linux实践系列】:用c/c++制作一个简易的进程池

一文帮组你掌握并且实现Linux的简易进程池!

努力努力再努力wz的博客 7912

保姆级教程:用YOLOv5s搞定钢材表面缺陷检测,从数据集NEU-DET到模型部署全流程

本文提供了一份详细的YOLOv5s钢材表面缺陷检测教程,涵盖从NEU-DET数据集处理到模型部署的全流程。针对工业环境中的实际限制,如计算资源有限和标注样本少等问题,提供了优化方案和实用技巧,帮助开发者快速实现高效的钢材缺陷检测系统。

weixin_30568591的博客 465

【QNX Hypervisor 2.2 用户手册】2.6 处理qvm终止

你可以注册通知,以在qvm进程实例退出时得到通知。这样你就可以采取相应的措施来响应这种终止事件。

从0到1,突破自己 1107

【Linux】实现一个简易的进程池

利用管道通信的方式实现了一个简易的进程池程序,父进程通过管道向子进程派发任务,子进程通过管道读取任务并执行.

C/C++领域,数据结构,linux,MySQl学习者 1130

(12)单相降压自耦变压器的等效电路和仿真模型

本文根据电机学基本原理,从单相双绕组变压器的接线方式出发,建立基本方程并导出T型等效电路。然后将双绕组变压器演变成一种降压自耦变压器,详细地建立起单相降压自耦变压器的数学模型以及导出相对应的等效电路,最后用Matlab/simulink的simscape元件库元件建立了基于单相自耦变压器的T型等效电路的仿真模型。用实例仿真验证了本文工作的正确性。

weixin_45670943的博客 1046

【Linux】详解自定义Shell管道 | 构建简易进程池

利用管道机制构建自定义Shell管道的方法,并在此基础上实现了简易版本的进程池。通过自定义Shell管道,用户能够灵活组合命令处理数据流;而简易进程池则通过多进程并发执行任务,提高了处理效率。文章详细讨论了从解析命令到执行任务的全过程,并通过实验验证了系统的有效性和实用性。

No Game No Life 1756

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

linux进程间通信——匿名管道、简易进程池、pipe应用

linux进程间通信——管道实战——进程池实现!!!

strive_mianyang的博客 2676

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

手搓一个简易 Linux 进程池:巩固进程知识

本文介绍了如何通过实现一个迷你进程池来巩固Linux进程编程的核心概念。该进程池包含主进程、工作进程和管道三个角色,采用轮询策略分发任务。代码展示了进程创建、管道通信、任务执行和进程回收的完整流程,重点讲解了避免僵尸进程、管道阻塞和描述符泄漏等常见问题。通过这个实践项目,读者可以深入理解fork、pipe、waitpid等系统调用的实际应用,掌握父子进程协同工作的完整生命周期。文章还提出了支持exec执行、双向通信等进阶扩展思路,为后续学习线程池和IO多路复用打下基础。

lzh20040919的博客 597

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【Linux】基于匿名管道通信实现的简易进程池

进程池是一种技术应用,它由资源进程和管理进程组成。资源进程是预先创建好的空闲进程,用于处理分配给它们的任务。管理进程则负责创建资源进程、分配任务给空闲的资源进程,并在任务完成后回收这些资源进程。

2301_76606232的博客 846

『 Linux 』匿名管道应用 - 简易进程池

当一种资源需要被使用时这意味着这个资源可能会被进行多次使用或者需要同时使用多个该资源,当出现这种情况时内核将会频繁的对该资源进行申请并释放,大大降低整体的效率;整体构造采用先描述后组织的方式对进程池进行设置,并以自顶向下的方式进行设计,即先将所需接口以声明的形式标出而后再对接口进行具体实现;本文模拟实现的进程池通过多个匿名管道实现进程间通信使得一个进程与多个其对应的血缘关系进程进行协同从而形成一个进程池[父写子读];子进程为父进程的一份拷贝,当父子进程中其中一个进程被修改时(即对物理内存进行修改);

小侯宝的博客 1196

【Linux】使用管道实现一个简易版本的进程池

例如,假设父进程创建了 10 个子进程,每个子进程都会继承 10 个管道的文件描述符,但实际上每个子进程只需要一个管道的读端文件描述符。如果父进程创建了多个管道(每个子进程对应一个管道),那么每个子进程都会继承所有管道的文件描述符,即使这些管道是用于其他子进程的。具体来说,它的目的是确保子进程只保留与自己相关的文件描述符,关闭其他无关的文件描述符,从而避免资源泄露和潜在的问题。它的主要目的是在创建新的子进程时,确保子进程能够关闭不需要的文件描述符,避免资源泄露和潜在的问题。,父进程只需写入命令,不需要读。

hlyd520的博客 1237

【Linux】匿名管道与命名管道,进程池的简易实现

功能:创建一无名管道原型参数:fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端返回值:成功返回0,失败返回错误代码。

m0_74774759的博客 955

Linux:基于匿名管道创建出简易进程池(进程间通信五)

提前预分配资源,避免频繁创建 / 销毁的开销,实现资源复用、提升效率。

hello world/linux 863

手搓简易 Linux 进程池:从 0 到 1 实现基于管道的任务分发系统

这部分是测试用的任务层,定义了进程池要执行的具体任务,以及随机生成任务的工具函数。/////////////////////////////任务测试代码///////////////////////////////////////// 定义任务类型:无参数无返回值的函数对象// 具体任务1:打印日志(带进程ID标识)std::cout << "我是一个打印日志的任务, pid" << getpid() << std::endl;// 具体任务2:模拟下载。

wbs_scy的博客 393

【Linux系统编程】:进程池(简易版)

进程池的简略模拟

m0_66182473的博客 489

Linux:简易进程池

【代码】Linux:简易进程池。

2401_84107961的博客 491

【Linux C | 网络编程】简易进程池的实现详解(一)

进程池(Process Pool)是一种并发编程的模型,用于管理和复用多个进程,以提高系统的效率和性能。它主要解决的问题是减少因频繁创建和销毁进程而带来的性能开销,特别是在需要处理大量并发任务时尤为有效。

qq_42037383的博客 1118

6.【Linux】进程间通信(管道&&命名管道||简易进程池||简易客户端服务端通信)

1.Linux原生支持的管道----匿名和命名管道2.System V-----共享内存、消息队列、信号量3.Posix------多线程、网路通信。

m0_73865858的博客 737
上一篇: Linux原生线程 与 互斥锁
下一篇: progressMSG进程间通信
supdriver
博客等级 码龄3年 192粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值