进程池的制作(linux进程间通信,匿名管道... ...)
目录
一、进程间通信的理解
1.为什么进程间要通信
2.如何进行通信
二、匿名管道
1.管道的理解
2.匿名管道的使用
3.管道的五种特性
4.管道的四种通信情况
5.管道缓冲区容量
三、进程池
1.进程池的理解
2.进程池的制作
四、源码
1.ProcessPool.hpp
2.Task.hpp
3.test.cc
本文旨在分享我对管道技术的理解和见解。由于个人知识和经验的限制,文中可能存在错误或遗漏。我真诚地邀请各位读者在阅读过程中发现任何问题时,指出错误并提供宝贵的意见。您的反馈将是我不断进步和完善的重要动力。
一、进程间通信的理解
1.为什么进程间要通信
首先进程之间是相互独立的,尽管是父子进程之间,它们虽然资源共享,但当子进程需要修改数据时仍然需要进行写时拷贝,保持独立性。
而让进程间通信可以实现数据之间的交互,资源共享,事件通知,又或者是让一个进程对另一个进程进行控制。
进程间通信是操作系统中实现进程间协作和数据交换的重要机制,它使得多个进程能够共同完成任务,提高系统的效率和可靠性。
2.如何进行通信
进程间通信的原理其实很简单,只需要两个进程共同访问一个资源,而一个进程对资源的更改能被另一进程感知到,从而做出相应的操作。
所以通信的前提是进程之间能够访问同一个资源,而且该资源是公共的,而不是某进程内部的。
二、匿名管道
1.管道的理解
我们把进程之间通信的介质(资源)叫作管道。开发者在设计管道技术时文件系统已经比较成熟,所以为了方便管理该资源就使用文件来实现,而对文件的读写就是通信的过程,但它与一般的文件还是有些区别,文件都是储存到磁盘上的,而进程之间通信用的文件并不需要把它储存到磁盘上,它只是作为一个传输介质。
它比较特殊,所以起名为管道。管道其实是一个内存级的文件。

注意:父子进程之间的管道叫作匿名管道,顾名思义就是没有名字,也不需要名字,因为子进程能够继承下来父进程开辟的管道资源。
2.匿名管道的使用
创建匿名管道常用的接口是:
int pipe(int pipefd[2]);
需要包含头文件:
#include<unistd.h>
- 返回值:创建成功返回0,失败返回-1
- 参数:这个是一个输出型参数,传入一个int类型长度为2的数组,然后得到pipefd[0]:以读的方式打开的文件描述符,pipefd[1]:以写的方式打开的文件描述符。
示例:
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int main()
{int pipefd[2];pipe(pipefd);int rfd = pipefd[0],wfd = pipefd[1];pid_t id = fork();if(id == 0){close(wfd);//关闭子进程的写文件,只让它读int k=0;while(true){read(rfd,&k,sizeof(k));printf("read:%d\n",k);}}else{close(rfd);//关闭父进程的读文件,只让它写。int num=0;while(true){write(wfd,&num,sizeof(num));num++;sleep(1);}}return 0;
}
因为只是一个小测试,代码写的并不严谨(没有检查调用是否成功,没有关闭文件,没有进程等待)大家不用太在意,能说明问题就行。
要记住pipefd[2]中哪个是读哪个是写有一个小技巧,0像嘴巴,所以下标为0的是读,1像钢笔,所以1下标是写。
3.管道的五种特性
- 匿名管道,只能用来进行具有血缘关系的进程间通信(用于父与子)。
- 管道文件,自带同步机制。如上代码示例,父进程写一次休眠一秒,而子进程是一直不断地读,快的一端会迁就于慢的一端,最后实现同步。
- 管道是面向字节流的。怎么读与怎么写并没有联系,比如写入“hello world”,但可能读到“hel”,这取决于你要读多少字节。
- 管道是单向通信的。也就是a(表示进程)写的时候b读。b写的时候a在读。而不是既在写同时也在读。
- 管道(文件)的生命周期是随进程的。进程结束管道也随之销毁。
4.管道的四种通信情况
- 写慢,读快 --- 读端就要阻塞(等待写端写入)。
- 写快,读慢 --- 到管道容量满了后,写端就要阻塞(等待读端读取数据,然后就可以覆盖式地继续往管道写入)。
- 写关闭,读继续 --- read就会返回0,表示文件结尾。
- 写继续,读关闭 --- 写端不再有意义,系统会杀掉写端进程。
5.管道缓冲区容量
管道缓冲区容量为64kb,大家可以根据管道的性质与通信特点,自行进行测试,我这里就不再展示。
三、进程池
1.进程池的理解
在程序使用内存的时候,比如vector扩容机制,会提前给你开辟一块空间供你使用,尽管现在用不到,相当于做一下预备。减少开辟空间的频次,从而达到提高效率的效果。
那么进程池也同样,给父进程提前开辟一些子进程,提供父进程使用。其中我们使用匿名管道建立联系。

在父进程给子进程派发任务时,为了提高效率会让每个子进程均匀地分配到任务(称为负载均匀),而不是把大部分的任务都派发到一个子进程上,通常会有以下策略:
- 轮询:按顺序一一分配。
- 随机:随机进行分配。
- 负载因子:设计一个负载因子,让子进程按负载因子的大小排成一个小根堆,每次取出堆头的子进程派发任务,然后更新负载因子插回到堆中。
2.进程池的制作
在面向对象的编程中最重要的就是对对象的描述与组织,这里我们的核心就是对管道进行管理。那么首先需要一个类对管道进行描述。
class Channel
{
public:Channel(int wfd, int id) : _wfd(wfd), _id(id){}//... ...~Channel(){}
private:int _wfd;int _id;
};
_wfd是该管道对应写端的fd,_id是该管道对应的子进程的pid。
这里我们不必把rfd(读端fd)加入,因为我们现在对管道的描述组织,目的是方便父进程管理,而rfd是给子进程用的,所以不用添加为变量。
这里我们就以轮询的方式派发任务,刚才创建的Channel相当于对管道的描述,接下来创建ChannelManage进行组织。这里选择使用数组来管理,派发任务方式选择轮询,所以需要记录下一个需要派发到的管道的下标。
class ChannelManage
{
public:ChannelManage():_next(0){}//... ...~ChannelManage(){}
private:vector<Channel> _channels;int _next;
};
接下来还需要创建一个类对整体的进程池做管理。
class ProcessPool
{
public:ProcessPool(int num) : _n(num){}// ... ...~ProcessPool(){}
private:ChannelManage _cm;int _n;
};
其中_n表示需要创建多少子进程,这是由使用者来决定的。
在ProcessPool中我们准备实现这些方法
- void Create():用于创建子进程。
由于我们是要生成多个通道所以需要循环来进行,而单趟循环需要做以下这些操作:
1.创建管道,然后创建子进程。(这样能让子进程继承到管道信息)
2.关于子进程:写端关闭,然后执行Work(),最后把读端关闭,并exit退出。
3.关于父进程:读端关闭,然后把wfd,pid存入_cm中。
- void Work(int rfd):用于子进程读取任务码并执行命令。
- void Run():用于获取并派发任务。
- void Stop():用于关闭写端并回收子进程。
最后为方便测试我们还需要一个管理任务的类和方法。我们可以单独创建一个Task.hpp文件。
class TaskManage
{
public:TaskManage(){ //随机数种子srand((unsigned int)time(nullptr));}int GetCode(){ //随机生成任务码(数组下标)return rand()%_tasks.size();}void ExeTask(int code){ //执行任务_tasks[code]();}// ... ...~TaskManage(){}
private:vector<function<void()>> _tasks;//用于储存任务的数组
};
然后需要在ProcessPool中放入TaskManage成员变量,并在ProcessPool的构造函数中完成对_tasks中内容的插入。具体操作参考下面源码。
四、源码
1.ProcessPool.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/wait.h>
#include <vector>
#include "Task.hpp"
#define NUM 5
using namespace std;
class Channel
{
public:Channel(int wfd, int id) : _wfd(wfd), _id(id){}void Write(int code){write(_wfd,&code,sizeof(code));}void Close(){close(_wfd);}void WaitPid(){waitpid(_id,nullptr,0);cout<<"等待进程"<<_id<<"成功"<<endl;}~Channel(){}private:int _wfd;int _id;
};
class ChannelManage
{
public:ChannelManage():_next(0){}void Insert(int wfd, int id){_channels.emplace_back(wfd, id);}int GetChannel()//轮询访问子进程{int tmp = _next;_next++;_next %= _channels.size();return tmp;}void WriteManage(int code,int index){_channels[index].Write(code);}void Close(){for(int i=0;i<_channels.size();i++){_channels[i].Close();}}void WaitPid(){for(int i=0;i<_channels.size();i++){_channels[i].WaitPid();}}~ChannelManage(){}
private:vector<Channel> _channels;int _next;
};class ProcessPool
{
public:ProcessPool(int num) : _n(num){_tm.InsertTask(PrintLog);_tm.InsertTask(Download);_tm.InsertTask(Upload);}void Work(int rfd){while (true){int code;ssize_t n = read(rfd, &code, sizeof(code));if (n > 0){if(n != sizeof(code)) continue;else{//执行任务_tm.ExeTask(code);}}else{cout<<"子进程"<<getpid()<<"退出"<<endl;break;}}}void Create(){for (int i = 0; i < _n; i++){int pipefd[2];pipe(pipefd);pid_t id = fork();if (id == 0){close(pipefd[1]);Work(pipefd[0]);close(pipefd[0]);exit(0);}else{close(pipefd[0]);_cm.Insert(pipefd[1], id);}}}void Run(){int ChannelCode = _cm.GetChannel();int TaskCode = _tm.GetCode();_cm.WriteManage(TaskCode,ChannelCode);}void Stop(){_cm.Close();_cm.WaitPid();}~ProcessPool(){}
private:ChannelManage _cm;int _n;TaskManage _tm;
};
2.Task.hpp
#pragma once
#include <iostream>
#include <vector>
#include <functional>
#include <ctime>
#include <cstdlib>
using namespace std;
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;
}void Download()
{std::cout << "我是一个下载的任务" << std::endl;
}void Upload()
{std::cout << "我是一个上传的任务" << std::endl;
}
class TaskManage
{
public:TaskManage(){srand((unsigned int)time(nullptr));}void InsertTask(void(*fun)()){_tasks.push_back(fun);}int GetCode(){return rand()%_tasks.size();}void ExeTask(int code){_tasks[code]();}~TaskManage(){}
private:vector<function<void()>> _tasks;
};
3.test.cc
#include "ProcessPool.hpp"
int main()
{ProcessPool pp(NUM);pp.Create();int k = 10;while(k--){pp.Run();}pp.Stop();return 0;
}
相关文章:
进程池的制作(linux进程间通信,匿名管道... ...)
目录 一、进程间通信的理解 1.为什么进程间要通信 2.如何进行通信 二、匿名管道 1.管道的理解 2.匿名管道的使用 3.管道的五种特性 4.管道的四种通信情况 5.管道缓冲区容量 三、进程池 1.进程池的理解 2.进程池的制作 四、源码 1.ProcessPool.hpp 2.Task.hpp 3…...
【Linux】Linux C比较两个 IPv6 网关地址是否相等,包括前缀
功能说明 在 Linux 环境下使用 C 语言比较两个 IPv6 网关地址是否相等,包括前缀 实现步骤 解析 IPv6 地址:使用 inet_pton 将字符串形式的 IPv6 地址转换为二进制形式。解析前缀长度:从地址字符串中提取前缀长度(如 /64…...
【uniapp】uniapp使用java线程池
标题 由于js是性能孱弱的单线程语言,只要在渲染中执行了一些其他操作,会中断渲染,导致页面卡死,卡顿,吐司不消失等问题。在安卓端可以调用java线程池,把耗时操作写入线程池里面,优化性能。 实…...
面试题-Java集合框架
前言 Java集合框架(Java Collections Framework)是Java平台提供的一套用于表示和操作集合的统一架构。它位于java.util包中,并且自Java 1.2(也称为Java 2平台,标准版,即Java SE 2)起成为Java平…...
Java基础教程(007):方法的重载与方法的练习
文章目录 6.5 方法的重载6.6 方法练习数组遍历数组最大值 6.5 方法的重载 在 Java 中,方法的重载是指在同一个类中定义多个方法,这些方法具有相同的名称,但参数列表不同。方法的重载是一种实现多态的方式,允许一个方法名以不同的…...
【ESP32】ESP-IDF开发 | WiFi开发 | TCP传输控制协议 + TCP服务器和客户端例程
1. 简介 TCP(Transmission Control Protocol),全称传输控制协议。它的特点有以下几点:面向连接,每一个TCP连接只能是点对点的(一对一);提供可靠交付服务;提供全双工通信&…...
npm cnpm pnpm npx yarn的区别
npm、cnpm、pnpm、npx、yarn 这几个工具都与 Node.js 项目的包管理和命令执行相关,它们的区别具体如下: 本质与功能定位 npm:是 Node.js 官方的包管理工具,提供了安装、卸载、更新、发布等全方位的包管理功能,还能通…...
debian12.9编译freeswitch1.10.12【默认安装】
服务器操作系统 cat /etc/os-release PRETTY_NAME"Debian GNU/Linux 12 (bookworm)" NAME"Debian GNU/Linux" VERSION_ID"12" VERSION"12 (bookworm)" VERSION_CODENAMEbookworm IDdebian HOME_URL"https://www.debian.org/&quo…...
使用 C/C++ 调用 libcurl 调试消息
在使用 C/C 调用 libcurl 进行 HTTP 请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试。libcurl 提供了多种方法来捕获和输出这些信息,本文介绍具体的使用方式。 1. libcurl 调试工具简介 libcurl 是…...
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》030-自定义组件的插槽Mixin
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主&…...
大一计算机的自学总结:异或运算
前言 异或运算这个操作看上去很匪夷所思,实际上作用非常大。 一、异或运算的性质 1.异或运算就是无进位相加。 2.满足交换律、结合律。 3.0^nn,n^n0。 4.若集合B为集合A子集,集合A异或和为x,集合B异或和为y,则集…...
通过protoc工具生成proto的pb.go文件以及使用protoc-go-inject-tag工具注入自定义标签
1.ProtoBuf认识,安装以及用法 参考:[golang 微服务] 3. ProtoBuf认识,安装以及golang 中ProtoBuf使用 2. 使用protoc-go-inject-tag工具注入自定义标签 这里有一个案例: syntaxproto3; package test;option go_package ".;test";message MyMessage {int6…...
C语言练习(29)
13个人围成一圈,从第1个人开始顺序报号1、2、3。凡报到“3”者退出圈子,找出最后留在圈子中的人原来的序号。本题要求用链表实现。 #include <stdio.h> #include <stdlib.h>// 定义链表节点结构体 typedef struct Node {int num;struct Nod…...
Android实训九 数据存储和访问
实训9 数据存储和访问 一、【实训目的】 1、 SharedPreferences存储数据; 2、 借助Java的I/O体系实现文件的存储, 3、使用Android内置的轻量级数据库SQLite存储数据; 二、【实训内容】 1、实现下图所示的界面,实现以下功能: 1ÿ…...
实验一---典型环节及其阶跃响应---自动控制原理实验课
一 实验目的 1.掌握典型环节阶跃响应分析的基本原理和一般方法。 2. 掌握MATLAB编程分析阶跃响应方法。 二 实验仪器 1. 计算机 2. MATLAB软件 三 实验内容及步骤 利用MATLAB中Simulink模块构建下述典型一阶系统的模拟电路并测量其在阶跃响应。 1.比例环节的模拟电路 提…...
SOME/IP--协议英文原文讲解2
前言 SOME/IP协议越来越多的用于汽车电子行业中,关于协议详细完全的中文资料却没有,所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块: 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.1 Speci…...
matlab中,fill命令用法
在 MATLAB 中,fill 命令用于创建填充多边形的图形对象。使用 fill 可以在二维坐标系中绘制填充的区域,通常用于绘制图形的背景或显示数据分布。 基本语法 fill(X, Y, C)X 和 Y 是同样长度的向量,定义了多边形的顶点坐标。C 是颜色࿰…...
【Linux】Linux C判断两个IPv6地址是否有包含关系
功能说明 要判断两个 IPv6 地址是否具有包含关系,包括前缀的比较,可以通过以下步骤实现: 解析 IPv6 地址和前缀:将两个 IPv6 地址和它们的前缀长度解析为二进制形式。生成掩码:根据前缀长度生成掩码。按位比较&#…...
【玩转全栈】----Django基本配置和介绍
目录 Django基本介绍: Django基本配置: 安装Django 创建项目 创建app 注册app Django配置路由URL Django创建视图 启动项目 Django基本介绍: Django是一个开源的、基于Python的高级Web框架,旨在以快速、简洁的方式构建高质量的Web…...
mysql 学习6 DML语句,对数据库中的表进行 增 删 改 操作
添加数据 我们对 testdatabase 数据中 的 qqemp 这张表进行 增加数据,在这张表 下 打开 命令行 query console 在 软件中就是打开命令行的意思 可以先执行 desc qqemp; 查看一下当前表的结构。 插入一条数据 到qqemp 表,插入时要每个字段都有值 insert…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
