IPC 进程间通信(一):管道(匿名管道进程池)

1. 初识进程间通信

1.1进程间通信的目的:

1、数据传输:一个进程需要将它的数据发送给另一个进程
2、资源共享:多个进程之间共享同样的资源
3、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)

4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变

1.2 为什么要有进程间通信

为了实现两个或者多个进程实现数据层面的交互,因为进程独立性的存在,导致进程通信的成本比较高   很多场景下需要多个进程协同工作来完成要求。如下:

  • 这条命令首先使用 cat  读取 log.txt 的内容,然后通过管道 (|) 将输出传递给 grep 命令。grep 用于搜索指定的字符串。
  • grep Hello 这个命令搜索包含 "Hello" 的行。

1.3进程间通信的方式

管道通过文件系统通信)

  • 匿名管道pipe
  • 命名管道 

System V IPC 聚焦在本地通信

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC 让通信可以跨主机)

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

注意:

  •  System V 标准需要重新构建操作系统代码来实现进程通信,比较繁琐。
  • 在 System V 标准出现之前,而「管道通信」是直接复用现有操作系统的代码
  • 现在本地通信已经被网络通信取代,所以进程间通信方式只重点介绍管道通信和共享内存通信

知识补充:

(1)进程间通信的本质:必须让不同的进程看到同一份“资源”(资源:特定形式的内存空间)

(2)这个资源谁提供?一般是操作系统

  • 为什么不是我们两个进程中的一个呢?假设一个进程提供,这个资源属于谁?
  • 这个进程独有,破坏进程独立性,所以要借用第三方空间

(3)我们进程访问这个空间,进行通信,本质就是访问操作系统!

  • 进程代表的就是用户,资源从创建,使用(一般),释放--系统调用接口!

2.匿名管道

2.1.什么是管道

进程可以通过 读/写 的方式打开同一个文件,操作系统会创建两个不同的文件对象 file,但是文件对象 file 中的内核级缓冲区、操作方法集合等并不会额外创建,而是一个文件的文件对象的内核级缓冲区、操作方法集合等通过指针直接指向另一个文件的内核级缓冲区、操作方法集合等。

  • 这样以读方式打开的文件和以写方式打开的文件共用一个 内核级缓冲区

  • 进程通信的前提是不同进程看到同一份共享资源

所以根据上述原理,父子进程可以看到同一份共享资源:被打开文件的内核级缓冲区。父进程向被打开文件的内核级缓冲区写入,子进程从被打开文件的内核级缓冲区读取,这样就实现了进程通信!

  • 这里也将被打开文件的内核级缓冲区称为 「 管道文件」,而这种由文件系统提供公共资源的进程间通信,就叫做「 管道 

注意:

此外,管道通信只支持单向通信,即只允许父进程传输数据给子进程,或者子进程传输数据给父进程。

  • 当父进程要传输数据给子进程时,就可以只使用以写方式打开的文件的管道文件,关闭以读方式打开的文件,

  • 同样的,子进程只是用以读方式打开的文件的管道文件,关闭掉以写方式打开的文件。

  • 父进程向以写方式打开的文件的管道文件写入,子进程再从以读方式打开的文件的管道文件读取,从而实现管道通信。如果是要子进程向父进程传输数据,同理即可。

管道特点总结:

  • 一个进程将同一个文件打开两次,一次以写方式打开,另一次以读方式打开。此时会创建两个struct file,而文件的属性会共用,不会额外创建
  • 如果此时又创建了子进程,子进程会继承父进程的文件描述符表,指向同一个文件,把父子进程都看到的文件,叫管道文件
  • 管道只允许单向通信

  • 管道里的内容不需要刷新到磁盘

2.2 创建匿名管道

匿名管道:没有名字的文件(struct file)

匿名管道用于父子间通信,或者由一个父创建的兄弟进程(必须有“血缘“)之间进行通信

#include <unistd.h>
原型:int pipe(int fd[2]);功能:创建匿名管道
参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

使用如下:

int main()
{// 1. 创建管道int fds[2] = {0};int n = pipe(fds); // fds: 输出型参数if(n != 0){std::cerr << "pipe error" << std::endl;return 1;}std::cout << "fds[0]: " << fds[0] << std::endl;std::cout << "fds[1]: " << fds[1] << std::endl;return 0;
}// 运行如下:
fds[0]: 3
fds[1]: 4
  • 输出型参数:文件的描述符数字带出来,让用户使用-->3,4,因为0,1,2分别被stdin,stdout,stderr占用。

2.3 匿名管道通信案例(父子通信)

注意:匿名管道需要在创建子进程之前创建,因为只有这样才能复制到管道的操作句柄,与具有亲缘关系的进程实现访问同一个管道通信

情况一:管道为空 && 管道正常(read 会阻塞【read 是一个系统调用】)

具体代码演示如下:(子进程写入,父进程读取)

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdlib>// 父进程 -- 读取
// 子进程 -- 写入
void write(std::string &info, int cnt)
{info += std::to_string(getpid());info += ", cnt: ";info += std::to_string(cnt);info += ')';
}int main()
{// 1. 创建管道int fds[2] = {0};int n = pipe(fds); // fds: 输出型参数if (n != 0){std::cerr << "pipe error" << std::endl;return 1;}// 2. 创建子进程pid_t id = fork();if (id < 0){std::cerr << "fork error" << std::endl;return 2;}else if (id == 0){// 子进程// 3. 关闭不需要的 fd, 关闭 readint cnt = 0;while (true){close(fds[0]);std::string message = "(hello linux, pid: ";write(message, cnt);::write(fds[1], message.c_str(), message.size());cnt++;sleep(2);}exit(0);}else{// 父进程// 3. 关闭不需要的 fd, 关闭 writeclose(fds[1]);char buffer[1024];while(true){ssize_t n = ::read(fds[0], buffer, 1024);if(n > 0){buffer[n] = 0;std::cout << "child->father, message: " << buffer << std::endl;}}// 记录退出信息pid_t rid = waitpid(id, nullptr, 0);std::cout << "father wait chile success" << rid << std::endl;}return 0;
}

从上面可以知道:

  1. 子进程写入的信息是变化的信息
  2. 父进程打印信息的时间间隔和子进程一样,那么子进程没传入信息的时候,父进程处于阻塞 --> (IPC 本质:先让不同的进程,看到同一份资源,可以保护共享资源)

情况二:管道为满 && 管道正常(write 会阻塞【write 是一个系统调用】)

如下对代码做点修改(红框内的代码)

管道有上限,Ubuntu -> 64 KB

如果我们让父进程正常读取,那么结果又是怎样的呢?

当我们到 65536 个字节时,管道已满,父进程读取了管道数据,子进程会继续进行写入,然后进行继续读取,就有点数据溢出的感觉

情况三:管道写端关闭 && 读端继续(读端读到0,表示读到文件结尾)

代码修改如下:

else if (id == 0)
{int cnt = 0, total = 0;while (true){close(fds[0]);std::string message = "h";// fds[1]total += ::write(fds[1], message.c_str(), message.size());cnt++;std::cout << "total: " << total << std::endl; // 最后写到 65536 个字节sleep(2);break; // 写端关闭}exit(0);
}
else
{// 父进程// 3. 关闭不需要的 fd, 关闭 writeclose(fds[1]);char buffer[1024];while (true) {sleep(1);ssize_t n = ::read(fds[0], buffer, 1024);if (n > 0) {buffer[n] = 0;std::cout << "child->father, message: " << buffer << std::endl;}else if (n == 0) {std::cout << "n: " << n << std::endl;std::cout << "child quit??? me too " << std::endl;break;}std::cout << std::endl;}pid_t rid = waitpid(id, nullptr, 0);std::cout << "father wait chile success" << rid << std::endl;
}

结论:如果写端关闭,读端读完管道内部数据,再读取就会读取到返回值 0,表示对端关闭,也表示读到文件结尾

情况四:管道写端正常 && 读端关闭(OS 会直接杀掉写入进程)

情况二:

如何杀死呢?

a. OS 会给 目标进程发送信号:13) SIGPIPE

b. 证明如下;

else if (id == 0)
{int cnt = 0, total = 0;while (true){close(fds[0]);std::string message = "h";// fds[1]total += ::write(fds[1], message.c_str(), message.size());cnt++;std::cout << "total: " << total << std::endl; // 最后写到 65536 个字节sleep(2);}exit(0);
}
else
{close(fds[1]);char buffer[1024];while (true){sleep(1);ssize_t n = ::read(fds[0], buffer, 1024);if (n > 0){buffer[n] = 0;std::cout << "child->father, message: " << buffer << std::endl;}else if (n == 0){std::cout << "n: " << n << std::endl;std::cout << "child quit??? me too " << std::endl;break;}close(fds[0]); // 读端关闭break;std::cout << std::endl;}// 记录退出信息int status = 0;pid_t rid = waitpid(id, &status, 0);std::cout << "father wait chile success: " << rid << " exit code: " <<((status << 8) & 0xFF) << ", exit sig: " << (status & 0x7F) << std::endl;
}
小结

🦋 管道读写规则

  • 当没有数据可读时
    • read 调用阻塞,即进程暂停执行,一直阻塞等待
    • read 调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
    • write 调用阻塞,直到有进程读走数据
    • 调用返回-1,errno值为 EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号

2.4 匿名管道特性

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

  2. 管道文件的生命周期是随进程的

  3. 管道内部,自带进程之间同步的机制(多执行流执行代码的时候,具有明显的顺序性)

     4.管道文件在通信的时候,是面向字节流的。(写的次数和读取的次数不是一一匹配的)

  1. 管道的通信模式,是一种特殊的半双工模式,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

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

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

相关文章

Linux-数据结构-单链表练习-双链表

一.单链表练习和一些功能实现 【1】单链表实现字典查询 &#xff08;1&#xff09;定义存放数据的结构体&#xff0c;在每次向里面存放数据时候需要清空 &#xff08;2&#xff09;对字典进行切割 空格切割字母&#xff0c;再从剩余里切割到解释&#xff0c;这里windows的txt文…

网络爬虫相关

一、爬虫的基础内容 1、基本概念和用途 1.1、概念&#xff1a; 模拟浏览器、发送请求&#xff0c;获取响应。&#xff08;只能获取客户端所展示出来的数据&#xff09; 1.2、特点&#xff1a;知识碎片化&#xff0c;针对不同的网站对应的方法也不同。 爬虫&#xff1a;模拟…

Lora 中 怎么 实现 矩阵压缩

Lora 中 怎么 实现 矩阵压缩 1. 导入必要的库 import torch import re from datasets import Dataset from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer, \get_cosine_schedule_with_warmup, EarlyStoppingCallback from peft

golang-嵌套结构体

结构体嵌套 golang中没有类&#xff0c;他通过结构体来实现其他编程语言中类的相关功能。 具名结构体 基本语法 基本语法 golang的结构体嵌套特别简单。 type 结构体类型1 struct{字段 类型1字段 类型2 }//这样就实现了结构体的嵌套 type 结构体类型2 struct{字段 类型1字…

基于Spring Boot的大学校园生活信息平台的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

【 利用socket来实现简单远控】

利用socket来实现简单远控 &#x1f539; 免责声明⚠️ 重要提示一、什么是socket&#xff1f;二、如何使用socket来实现两台计算机之间的通信&#xff1f;服务端1、首先需要创建一个socket&#xff1b;2、绑定IP以及端口3、开启监听4、接受客户端连接5、客户端连接上之后就是命…

数据可视化在特征分布对比中的应用

数据可视化在特征分布对比中的应用 1. 引言 在机器学习系统开发和维护过程中,特征分布对比是评估数据质量和模型鲁棒性的关键环节。当训练数据与测试数据分布存在偏差,或生产环境中的数据分布随时间发生变化时,模型性能通常会显著下降。有效的数据可视化不仅能帮助检测这些…

依赖倒置 DIP、依赖注入 DI、控制反转 IoC 和工厂模式

1. 依赖倒置 依赖倒置原则&#xff08;Dependency Inversion Principle, DIP&#xff09;是 SOLID 原则中的一项&#xff0c;其核心思想是通过抽象解耦高层模块和低层模块&#xff0c;使二者都依赖于抽象而非具体实现。 依赖反转/倒置的体现&#xff1a;传统依赖方向是高层模块…

UnitTest框架管理测试用例——python自动化测试

UnitTest框架 UnitTest是Python自带一个单元测试框架&#xff0c;常用它来做单元测试。 注意:对于测试来说&#xff0c;UnitTest框架的作用是 自动化脚本(用例代码)执行框架————(使用UnitTest框架来管理 运行多个测试用例的) 为什么使用UnitTest框架 能够组织多个用例去执…

Vue 过滤器深度解析与应用实践

文章目录 1. 过滤器概述1.1 核心概念1.2 过滤器生命周期 2. 过滤器基础2.1 过滤器定义2.2 过滤器使用 3. 过滤器高级用法3.1 链式调用3.2 参数传递3.3 动态过滤器 4. 过滤器应用场景4.1 文本格式化4.2 数字处理4.3 数据过滤 5. 性能优化与调试5.1 性能优化策略5.2 调试技巧 6. …

ngx_http_module_t

定义在 src\http\ngx_http_config.h typedef struct {ngx_int_t (*preconfiguration)(ngx_conf_t *cf);ngx_int_t (*postconfiguration)(ngx_conf_t *cf);void *(*create_main_conf)(ngx_conf_t *cf);char *(*init_main_conf)(ngx_conf_t *cf, void *conf);…

每日定投40刀BTC(9)20250312 - 20250315

定投截图 区块链相关新闻 BTC价格一度跌破8万美元 3月14日&#xff0c;BTC价格盘中跌破8万美元&#xff0c;最低报79,954.60美元&#xff0c;日内下跌1.34%&#xff0c;市场情绪一度转为谨慎 BTC价格波动背后的原因 经济环境变化、市场情绪波动以及政策监管动态是导致BTC价…

Matlab 汽车二自由度转弯模型

1、内容简介 Matlab 187-汽车二自由度转弯模型 可以交流、咨询、答疑 2、内容说明 略 摘 要 本文前一部分提出了侧偏角和横摆角速度作为参数。描述了车辆运动的运动状态&#xff0c;其中文中使用的参考模型是二自由度汽车模型。汽车速度被认为是建立基于H.B.Pacejka的轮胎模…

CentOS 6 YUM源切换成国内yum源

由于 CentOS 6 已于 2020 年 11 月进入 EOL&#xff08;End of Life&#xff09;&#xff0c;官方软件源已不再提供更新&#xff0c;因此你可能会遇到 yum makecache 命令失败的问题。以下是解决该问题的详细步骤&#xff1a; ### 解决方案 1. **备份原有 yum 源文件** bash …

Leetcode 3483. Unique 3-Digit Even Numbers

Leetcode 3483. Unique 3-Digit Even Numbers 1. 解题思路2. 代码实现 题目链接&#xff1a;3483. Unique 3-Digit Even Numbers 1. 解题思路 这一题其实是一个easy的题目&#xff0c;因为限制条件有限&#xff0c;最暴力的方法就是直接遍历一下100到999的全部数字&#xff…

《基于深度学习的高分卫星图像配准模型研发与应用》开题报告

目录 1. 选题的背景和意义 1.1 选题的背景 1.2 国内外研究现状 1.3 发展趋势 2&#xff0e;研究的基本内容 2.1 主要研究内容 &#xff08;1&#xff09;训练与测试数据集构建 &#xff08;2&#xff09;基于深度学习的高精度卫星影像配准模型 &#xff08;3&#xff0…

【Python 算法零基础 1.线性枚举】

我装作漠视一切&#xff0c;以为这样就可以不在乎 —— 25.3.17 一、线性枚举的基本概念 1.时间复杂度 线性枚举的时间复杂度为 O(nm)&#xff0c;其中 n是线性表的长度。m 是每次操作的量级&#xff0c;对于求最大值和求和来说&#xff0c;因为操作比较简单&#xff0c;所以 …

前端性能优化回答思路

前端性能优化是面试中经常涉及的一个话题&#xff0c;面试官通常希望了解你在实际项目中如何处理性能瓶颈&#xff0c;如何识别和优化性能问题。以下是一些前端性能优化的常见问题以及你可以用来回答的思路&#xff1a; 如何提升页面加载速度&#xff1f; 回答思路&#xff1…

02-Canvas-fabric.ActiveSelection

fabric.ActiveSelection fabric.ActiveSelection 用于表示当前选中的多个对象&#xff08;即多选状态&#xff09;。 当用户在画布上选择多个对象时&#xff0c;Fabric.js 会自动将这些对象包装在fabric.ActiveSelection 实例中&#xff0c;以便统一操作&#xff08;如移动、缩…

Leetcode——151.反转字符串中的单词

题解一 思路 最开始的想法是把一个字符串分为字符串数组&#xff0c;但是不知道一共有几个单词&#xff08;当时没想起来split()&#xff09;&#xff0c;所以选择了用ArrayList储存字符串&#xff0c;在输出时没有考虑ArrayList可以存储空字符串&#xff0c;所以最开始的输出…