15.进程间通信(一)

一、进程间通信介绍

进程间通信目的:
数据传输:一个进程需要将它的数据发送给另⼀个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
怎么通信?
进程间通信本质:先让不同的进程,看到同一份资源(内存)(然后才有通信的条件)
不能由任何一个进程提供,进程间数据隔离->OS提供系统调用->设计统一的通信接口。
什么是通信?

二、具体通信方式

1)基于文件的,管道通信

2)System V 本机通信

1.背景

基于已有的技术,直接进行通信。

2.原理

单独设计了一个内存级的文件,管道(复用了文件管理的代码)。

独特的系统调用:

/* On all other architectures */

int pipe(int pipefd[2]);  //数组第一个参数是读的fd,第二个参数的写的fd。

返回值:成功返回0,失败返回-1,错误码被设置。

管道:通过创建子进程,子进程拷贝一份和父进程一样的文件描述符表,指向同一个“文件”(管道),关闭相应的读写端,使得单向通信。

3.demo代码,测试接口。

测试代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void ChildWrite(int wfd)
{std::cout << "子进程wfd:" << wfd << std::endl;char buff[1024];int cnt = 0;while (true){sleep(1);snprintf(buff, sizeof(buff), "第%d次:子进程写入\n", cnt++);ssize_t ret = write(wfd, buff, strlen(buff));(void)ret;}
}void FatherRead(int rfd)
{std::cout << "父进程rfd:" << rfd << std::endl;char buff[1024];int cnt = 1;while (cnt--){sleep(5);ssize_t ret = read(rfd, buff, sizeof(buff) - 1);buff[ret] = 0;std::cout << buff << std::endl;if (ret == 0){std::cout << "写关闭了,读关闭" << std::endl;break;}}
}int main()
{// 1.创建管道int pipefd[2] = {0};int ret = pipe(pipefd);if (ret == -1)exit(1);// 2.创建子进程,子进程写,父进程读pid_t pid = fork();if (pid < 0)exit(1);else if (pid == 0){// 子进程close(pipefd[0]);ChildWrite(pipefd[1]);close(pipefd[1]);exit(0);}else{// 父进程close(pipefd[1]);FatherRead(pipefd[0]);close(pipefd[0]);int status;waitpid(pid, &status, 0);std::cout << "子进程 exit code:" << ((status >> 8) & 0xFF) << " exit signal:" << (status & 0x7F) << std::endl; }return 0;
}

5种特性(重点):

1)匿名管道,只能用来进行具有血缘关系的进程进行进程间通信(常用于父子)

2)管道文件,自带同步机制

3)管道是面向字节流的。(怎么读和怎么写没有必然关系)

4)管道是单向通信的。

属于半双工的一种特殊情况。

半双工:任何时刻,一个发,一个收。

全双工:任何时刻,可以同时收发(吵架)。

5)(管道)文件的生命周期是随进程的(引用计数)。

4中通信情况:

1)写慢,读快 ------ 读端就要阻塞(进程)

2)写快,读慢 ------ 满了的时候,写端就要阻塞等待

3)写关,读继续 ------ read读到返回值为0,表示文件结尾。

4)写继续,读关 ------ 写端写入无意义,OS不会做无意义的事情->OS会杀掉写端进程->发送异常信号 13 SIGPIPE 

在小于pipe_buf时,管道的写入被要求是原子性的(一次要写全写完)。

测试管道容量:一次写一个字节,写入测试信息。

4.基于匿名管道 --- 进程池 

原理:

父进程通过向指定的管道写入的方式来向子进程发送对应的任务。

父进程的wfd关闭,子进程会读到0,可以退出。

总体结构:

        要对匿名管道进行管理,先描述在组织,要有对应的类Channel,记录父进程的wfd和子进程的pid,在有对应管理多个类Channel的数据结构vector,对于匿名管道的管理就转换成了对于vector的增删查改。对于任务也做管理,与匿名管道管理类似。

逻辑:

        父进程创建若干个子进程,让子进程阻塞在read中,并死循环执行,子进程一直阻塞,等待父进程发送对应的任务码,根据任务码执行相应的任务。父进程采用轮询的方式,确保能够负载均衡,选择子进程发送随机的任务码,即向对应的匿名管道内写入任务码。

易错点:

回收时需注意,每次创建子进程时,子进程会继承上一次父进程的wfd,导致第1个管道有n个引用,第2个管道有n-1个引用 ... 第n个管道有一个引用。

解决方法1:倒着关闭

解决方法2:随便关闭。创建子进程后,子进程把之前从父进程继承下来的wfd全部关闭。

完整代码如下:

Processpool.hpp

#pragma once#include <vector>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"class Channel
{
public:Channel(int wfd, int pid): _wfd(wfd), _pid(pid){}~Channel(){}int Wfd() const { return _wfd; }int Pid() const { return _pid; }void Close() const { close(_wfd); }void Wait() const { waitpid(_pid, nullptr, 0); }private:int _wfd; // 控制子进程int _pid; // 拿到子进程pid,方便回收
};class ChannelManager
{
public:ChannelManager() : _next(0){}// 为了保证负载均衡,采用轮询的方式const Channel& SelectChannel(){const Channel& c = _channels[_next];// 选出下标++_next;_next %= _channels.size();return c;}void InsertChannel(int wfd, int pid){_channels.emplace_back(wfd, pid);}void CloseFd() const{for(const auto &e : _channels){e.Close();}}void CloseChannels() const{// 2.正着关,创建子进程时就关闭从父进程继承下来的wfdfor (size_t i = 0; i < _channels.size(); i++){// 关闭父进程的写fd,让子进程读到0个字节退出。_channels[i].Close();_channels[i].Wait();printf("等待成功,回收了子进程:%d\n", _channels[i].Pid());}// // 1.倒着关闭// for (int i = _channels.size() - 1; i >= 0; i--)// {//     // 关闭父进程的写fd,让子进程读到0个字节退出。//     _channels[i].Close();//     _channels[i].Wait();//     printf("等待成功,回收了子进程:%d\n", _channels[i].Pid());// }}~ChannelManager(){}private:std::vector<Channel> _channels;int _next;
};class ProcessPool
{
public:ProcessPool(){}~ProcessPool(){}void ChildRead(int rfd) const{int taskcode = 0;while (true){ssize_t ret = read(rfd, &taskcode, sizeof(taskcode));// 父进程写端关闭了,子进程要结束if (ret == 0){std::cout << "父进程写端关闭,子进程:" << getpid() << "退出" << std::endl;break;}// 读到的不是4字节,丢弃,重新读if (ret != sizeof(taskcode)){printf("丢弃\n");return;}// 执行相应任务printf("进程:%d ExcuteTask开始,ret:%d,taskcode:%d\n", getpid(), (int)ret, taskcode);_tm.ExecuteTask(taskcode);}}void Create(int num){for (int i = 0; i < num; i++){// 1.创建管道int pipefd[2] = {0};int ret = pipe(pipefd);if (ret != 0)exit(1);// 2.创建子进程pid_t pid = fork();// 3.关闭父读,子写if (pid < 0)exit(1);else if (pid == 0){// 关掉从父进程继承下来的wfd_cm.CloseFd();// 子进程关闭写close(pipefd[1]);// 子进程工作printf("ChildRead开始,进程为%d\n", getpid());ChildRead(pipefd[0]);close(pipefd[0]);// 子进程完成工作,退出exit(0);}// 父进程关闭读close(pipefd[0]);// emplace_back直接构造,插入到_channels_cm.InsertChannel(pipefd[1], pid);// 循环num次}}// 选择一个子进程,随机发送任务void Run(){// 1.选择一个子进程const Channel &c = _cm.SelectChannel();printf("挑选的子进程为:%d\n", c.Pid());// 2.获取任务码int taskcode = _tm.TaskCode();// 3.发送任务码给子进程,子进程执行(写给子进程)printf("父进程:%d 写入taskcode:%d\n", getpid(), taskcode);ssize_t ret = write(c.Wfd(), &taskcode, sizeof(taskcode));}void Close() const{_cm.CloseChannels();}private:TaskManager _tm;ChannelManager _cm;
};

task.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>void Open()
{std::cout << "这是一个打开的任务" << std::endl;
}
void Download()
{std::cout << "这是一个下载的任务" << std::endl;
}
void Upload()
{std::cout << "这是一个上传的任务" << std::endl;
}typedef void (*task_t)();
class Task
{
public:Task(task_t task) : _task(task){}~Task(){}void operator()() const{_task();}task_t getTask() const {return _task;}
private:task_t _task;
};class TaskManager
{
public:TaskManager(){// 设置种子数,采用随机任务形式srand((unsigned)time(nullptr));Register(Open);Register(Download);Register(Upload);}// 注册任务void Register(task_t t){_tasks.emplace_back(t);}// 返回任务码int TaskCode() const{int ret = rand() % _tasks.size();return ret;}void ExecuteTask(int taskcode) const{if (taskcode < 0 || taskcode >= _tasks.size()){std::cout << "读取的任务码无效" << std::endl;return;}printf("进程:%d执行任务,taskcode为:%d\n",getpid(),taskcode);_tasks[taskcode]();printf("任务:%d 执行完毕\n",taskcode);}~TaskManager(){}private:std::vector<Task> _tasks;
};

main.cc

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include "ProcessPool.hpp"int main()
{pid_t pid = fork();if (pid == 0){ProcessPool pool;pool.Create(5);int cnt = 5;while (cnt--){sleep(1);pool.Run();}sleep(10);pool.Close();exit(0);}int status;waitpid(pid, &status, 0);std::cout << "main exit code:" << ((status >> 8) & 0xFF) << " signal code:" << (status & 0x7F) << std::endl;return 0;
}

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

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

相关文章

05-jenkins学习之旅-vue前项目部署实践

1、创建被管理项目 2、构建流程说明 jenkins其实就是将服务部署拆分成了&#xff1a; 1、拉取代码(git) 2、打包编译(npm install) 3、自定义脚本(dist复制、执行启动脚本) 4、部署成功后的一些通知等 3、demo配置 3.1、General 3.2 源码管理 添加用户名密码方式如下图 3.2…

服务器中分布式存储数据技术都包含哪些内容?

随着大数据时代的到来&#xff0c;企业和组织对于服务器的存储要求也在不断地增高&#xff0c;传统的存储架构已经无法满足一些大规模的数据存储和处理需求&#xff0c;分布式存储技术应运而生&#xff0c;成为了大数据存储的重要基础设施&#xff0c;下面&#xff0c;就来介绍…

从比分滚动到数据革命:体育数据如何重构我们的观赛体验?

当凌晨三点的欧冠决赛与闹钟冲突时&#xff0c;当世界杯小组赛因时差难以全程跟进时&#xff0c;当代体育迷早已不再依赖电视直播 —— 打开手机里的比分网&#xff0c;实时跳动的体育大数据正构建着全新的观赛宇宙。这些曾经被视为 "辅助工具" 的平台&#xff0c;如…

vue2使用element中多选组件el-checkbox-group,数据与UI更新不同步

问题描述 使用element多选checkbox组件&#xff0c;点击勾选取消勾选&#xff0c;视图未变化&#xff0c;再次点击表单其他元素&#xff0c;多选组件勾选状态发生变化&#xff0c;视图和数据未同步 第一次尝试&#xff1a;再el-checkbox-group多选父组件上增加点击事件&…

CodeTop之LRU缓存

题目链接 146. LRU 缓存 - 力扣&#xff08;LeetCode&#xff09; 题目解析 算法原理 我们使用双向链表哈希表的形式来模拟缓存机制 首先我们要自己实现一个双链表, 自己写一个内部类, 这个内部类记录了key,value,prev,next(前驱和后继), 后续我们就通过这个内部类来构造双…

PyQt学习系列11-综合项目:多语言文件管理器

PyQt学习系列笔记&#xff08;Python Qt框架&#xff09; 第十一课&#xff1a;综合项目 - 多语言文件管理器 &#xff08;原课程规划中的第十五课&#xff0c;按用户要求调整为第十一课&#xff09; 课程目标 综合运用PyQt框架开发一个支持多语言的文件管理器实现以下核心功…

【Ubuntu修改串口延时(Latency Timer)为1毫秒(设备拔插或系统重启后自动生效)】

Ubuntu修改串口延时Latency Timer为1毫秒-设备拔插或系统重启后自动生效 在Ubuntu系统中&#xff0c;串口设备的延时参数(latency_timer)可以通过udev规则永久修改。以下是完整步骤&#xff1a; 创建udev规则文件 sudo vim /etc/udev/rules.d/99-ftdi-low-latency.rules添加以…

OpenCV CUDA模块图像处理------颜色空间处理之GPU 上交换图像的通道顺序函数swapChannels()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数用于在 GPU 上交换图像的通道顺序&#xff08;例如将 BGR 图像转为 RGB&#xff09;。 它适用于多通道图像&#xff08;如 3 通道或 4 通道…

Linux Ubuntu24.04配置安装MySQL8.4.5高可用集群主从复制!

MySQL 主从复制&#xff08;Replication&#xff09;是实现数据高可用、读写分离及异地容灾的核心机制之一。主库写、从库读&#xff0c;提升并发能力&#xff1b;读写分离&#xff0c;减轻主库压力。 本地 windows 系统有一个Linux Ubuntu子系统&#xff0c;版本为Ubuntu 24.…

R基于逻辑回归模型实现心脏病检测及SHAP值解释项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 心血管疾病是全球范围内导致死亡的主要原因之一&#xff0c;每年有数百万人因此失去生命。在众多的…

嵌入式学习笔记 -函数嵌套时以及异常响应时,LR使用的具体过程

函数嵌套时以及异常响应时&#xff0c;寄存器LR的作用存在显著区别&#xff0c;理解这个问题对于理解freeRTOS底层代码的实现大有帮助&#xff0c;具体使用过程如下&#xff1a; 一 函数嵌套时的LR使用的具体过程 在ARM架构(特别是M0处理器)中&#xff0c;函数嵌套调用时LR(L…

Java String函数的使用

文章目录 String字符串比较字符串查找转化字符串替换字符串拆分字符串截取&#xff08;常用&#xff09;字符串的不可变性 String str本来是字符串常量的引用&#xff0c;应该打印地址&#xff0c;但是编译器重写了toString方法&#xff0c;所以打印hello String 的构造方法 …

Oracle 11G RAC重启系统异常

vmware安装centos7环境部署Oracle RAC (11.2.0.4) 部署时所有资源情况都是正常的&#xff0c;关机重启虚拟机后集群资源状态异常&#xff0c;请教CSDN大佬 – 部署规划 域名地址备注rac16192.168.31.16rac17192.168.31.17rac16vip192.168.31.26viprac17vip192.168.31.27vip…

吉林省CCPC与全国邀请赛(东北地区赛)游记

总述&#xff1a; 本次赛段共获得一银&#xff08;吉林省赛&#xff09;、一铜&#xff08;东北地区赛&#xff09;、一铁&#xff08;全国邀请赛的成绩&#xff09;。总体成绩跟校内赛的情况相比队伍状态与发挥水准都有提升&#xff09;&#xff0c;但也体现出很多不足&#x…

「Python教案」循环语句的使用

课程目标 1&#xff0e;知识目标 能使用for循环和while循环设计程序。能使用循环控制语句&#xff0c;break、continue、else设计程序。能使用循环实际问题。 2&#xff0e;能力目标 能根据需求合适的选择循环结构。能对嵌套循环代码进行调试和优化。能利用循环语句设计&am…

OpenCV---findCountours

一、基本概念与用途 findContours是OpenCV中用于在二值图像中查找轮廓的核心函数。轮廓作为连续的点集&#xff0c;能够精确勾勒出物体的边界&#xff0c;广泛应用于目标检测、形状分析、图像分割等领域。 函数核心价值 目标检测&#xff1a;通过轮廓定位图像中的物体&#…

20250523-BUG:无法加载“GameLib/Framework.h“头文件(已解决)

BUG&#xff1a;无法加载"GameLib/Framework.h"头文件&#xff08;已解决&#xff09; 最近在打开新的C项目时报了这个错&#xff0c;我是按照以下步骤来排除的BUG&#xff0c;希望对您有所帮助~ 检查【C/C】-【附加包含目录】中的路径有无问题&#xff0c;一般需要加…

商品条形码查询接口如何用C#进行调用?

一、什么是商品条码查询接口&#xff1f; 1974年6月26日&#xff0c;美国俄亥俄州的一家超市首次使用商品条码完成结算&#xff0c;标志着商品条码正式进入商业应用领域。这项技术通过自动识别和数据采集&#xff0c;极大提升了零售行业的作业效率&#xff0c;减少了人工录入错…

SD07_NVM的安装及相关操作

以下是在 Windows 系统 上使用 NVM&#xff08;Node Version Manager&#xff09; 管理多个 Node.js 版本的详细步骤&#xff0c;从零开始操作&#xff1a; 一、准备工作 卸载旧版 Node.js 打开 控制面板 → 程序和功能&#xff0c;找到已安装的 Node.js 和 npm&#xff0c;彻底…

OSI 深度安全防御体系架构深度剖析

文章目录 前言什么是 OSI 深度安全防御体系架构各层的安全防御措施物理层数据链路层网络层传输层会话层表示层应用层 OSI 深度安全防御体系架构的优势全方位防护深度防御灵活性和可扩展性 总结 前言 大家好&#xff0c;我是沛哥儿。今天咱们来深入探讨一下 OSI 深度安全防御体…