进程间通信 (匿名管道)

一、进程间通信的概念

        进程间通信是一个进程把自己的数据交给另一个进程,它可以帮助我们进行数据传输、资源共享、通知事件和进程控制。

        进程间通信的本质是让不同的进程看到同一份资源。因此,我们要有:

        1、交换数据的空间。2、这个空间不能由通信双方任意一方提供。(要有一个独立的空间)

二、匿名管道 

  1、匿名管道的基本使用

        基于文件的,让不同进程看到同一份资源的通信方式,叫做管道。

        匿名管道通常用于具有血缘关系的进程间进行通信。例如:父子进程间通信

        匿名管道就是通过系统调用创建出一份管道文件,然后给调用的进程返回读端、写端的文件描述符,然后创建子进程,子进程会继承父进程的相关属性信息,也可以拿到读端和写端,然后父子进程就可以进行通信了。

        例如父进程写,子进程读。只要父进程关闭读端,然后往写端写数据,子进程关闭写端,往读端读数据,就可以实现父子进程间的通信。

接口:

        参数:输出型参数,传入一个大小为2的int类型数组,就会返回读端和写端的文件描述符。  例如传入的数组名位pipefd,读端的文件描述符:pipefd[0],写端的就是pipefd[1]。

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

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>void mywrite(int wfd)
{char message[1024] = {0};int i = 1;while (1){// 自定义设置写入的内容snprintf(message, sizeof(message), "send a message to father, mypid is : %d, i = %d\n", getpid(), i);++i;write(wfd, message, strlen(message));// 方便观察sleep(1);}
}void myread(int rfd)
{char message[1024] = {0};while (1){// 读ssize_t n = read(rfd, message, sizeof(message) - 1);message[n] = '\0';printf("%s", message);// 方便观察sleep(1);}
}int main()
{// 子进程写,父进程读int pipefd[2] = {0};int pret = pipe(pipefd);if (pret < 0){printf("create pipe fail, errno is %d, errinfo is %s\n", errno, strerror(errno));return errno;}pid_t id = fork();if (id == 0){// 子进程关闭读端close(pipefd[0]);mywrite(pipefd[1]);close(pipefd[1]);exit(0);}// 父进程关闭写端close(pipefd[1]);myread(pipefd[0]);close(pipefd[0]);// 等待,防止僵尸wait(NULL);return 0;
}

        可以看到子进程不断写,父进程不断读,并打印。 

        小细节:pipe()函数必须在 fork 之前,因为如果 fork 之后再创建管道,就是父子进程都会创建管道,父子进程拿不到同一份管道资源,就无法进行通信。

 

  2、进程池

        我们可以利用匿名管道,让父进程给多个子进程派发任务,也就是父进程写任务,然后多个子进程读任务。

创建多个子进程,并用read使它们阻塞,等待父进程派发任务

// 创建 sp_num 个子进程
void CreateSubProcess(int sp_num, vector<ChildP> &ChildPs)
{for (int i = 0; i < sp_num; ++i){// 创建管道int pipefd[2] = {0};pipe(pipefd);pid_t id = fork();if (id < 0){// 创建子进程失败printf("fork fail, errno is %d, errstr is %s\n", errno, strerror(errno));}else if (id == 0){// 子进程读取任务// 关闭写端close(pipefd[1]);// 读ReadTask(pipefd[0], getpid());exit(0);}// 父进程派发任务,关闭读端close(pipefd[0]);// 父进程需要记录每个父进程的写端wfd。为了方便查看,顺便记录名字和pidstring name = "process " + to_string(i);ChildPs.push_back(ChildP(pipefd[1], id, name));}
}

 读任务函数

void ReadTask(int rfd, int pid)
{while (true){char buffer[200];ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';printf("子进程: %d 正在执行:> %s\n", pid, buffer);}// 写端关闭,读端读到0表示结束else if (n == 0){printf("子进程: %d 退出...\n", pid);break;}// n < 0表示出错else{printf("read fail, errno is %d, errstr is %s\n", errno, strerror(errno));return;}sleep(1);}
}

记录子进程相关信息的类

class ChildP
{
public:ChildP(int wfd, pid_t pid, const string &name): _wfd(wfd), _pid(pid), _name(name){}int getwfd() { return _wfd; }pid_t getpid() { return _pid; }string getname() { return _name; }private:int _wfd;     // 父进程的写端pid_t _pid;   // 子进程的pidstring _name; // 子进程的名字
};

不断往不同的子进程派送任务

void WriteTask(ChildP &cp)
{char buffer[1024];static int i = 1;snprintf(buffer, sizeof(buffer) - 1, "Task %d", i);++i;write(cp.getwfd(), buffer, strlen(buffer));// 打印确认信息cout << "Aleady Send a Task to " << cp.getname() << " ,pid is " << cp.getpid() << endl;
}// 发送 TaskNum 个任务
void SendTask(vector<ChildP> &ChildPs, int sp_num, int TaskNum)
{// PNode 表示子进程在数组内的编号,为 0 - (sp_num-1)int PNode = 0;while (TaskNum--){// 指派指定的子进程执行任务WriteTask(ChildPs[PNode]);sleep(1);PNode = (PNode + 1) % sp_num;}
}

主函数:

int main()
{int sp_num = 5;vector<ChildP> ChildPs;CreateSubProcess(sp_num, ChildPs);int TaskNum = 7;SendTask(ChildPs, sp_num, TaskNum);for(auto& cp : ChildPs){// 关闭写端close(cp.getwfd());}for(auto& cp : ChildPs){// 阻塞式等待waitpid(cp.getpid(), nullptr, 0);cout << "wait successfully: " << cp.getname() << " ,pid is " << cp.getpid() << endl;}return 0;
}

运行结果:

文件描述符关闭时要注意的问题:

        按照上面的代码,有多个子进程时,当我们关闭第一个子进程的写端时,正常来说写端关闭,读端就会读到0退出,但第一个子进程并不会退出。为什么呢?这是因为其他子进程还有该管道的写端并且没关。

        其他子进程的为什么会有第一个子进程的写端呢?

        因为在父进程创建第一个子进程后,只关闭了读端,因此,到创建第二个子进程时,子进程继承了父进程的写端,所以子进程2不仅打开了自己的读端,还打开了子进程1的写端。由此类推,子进程3打开了子进程1和子进程2的写端以及自己的读端 ......因此,当最后一个子进程的写端关闭时,才能一步步回退,把所有子进程关闭。

 

        由于这种问题的存在,当我们只想结束某一个子进程时,如果该子进程不是最后一个,那就会出错。

        因此,我们可以做出改进:在创建子进程时,保存父进程的写端,然后在创建新的子进程后关闭。

改进后的创建子进程代码:

void CreateSubProcess(int sp_num, vector<ChildP> &ChildPs)
{// 记录父进程的写端vector<int> f_wfd;for (int i = 0; i < sp_num; ++i){// 创建管道int pipefd[2] = {0};pipe(pipefd);pid_t id = fork();if (id < 0){// 创建子进程失败printf("fork fail, errno is %d, errstr is %s\n", errno, strerror(errno));}else if (id == 0){// 关闭父进程指向其他管道的写端for(int e : f_wfd){close(e);}// 子进程读取任务// 关闭写端close(pipefd[1]);// 读ReadTask(pipefd[0], getpid());exit(0);}// 父进程派发任务,关闭读端close(pipefd[0]);// 父进程需要记录每个父进程的写端wfd。为了方便查看,顺便记录名字和pidstring name = "process " + to_string(i);ChildPs.push_back(ChildP(pipefd[1], id, name));f_wfd.push_back(pipefd[1]);}
}

感谢大家观看!

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

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

相关文章

端口号占用解决方法

进入cmd输入&#xff1a; 第一步输入&#xff1a;netstat -ano|findstr 8080 第二步输入&#xff1a;taskkill -pid **** -f (示例)&#xff1a; C:\Users\Administrator>netstat -ano|findstr 8080 TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING …

AcWing 790. 数的三次方根——算法基础课题解

AcWing 790. 数的三次方根 题目描述 给定一个浮点数 n&#xff0c;求它的三次方根。 输入格式 共一行&#xff0c;包含一个浮点数 n。 输出格式 共一行&#xff0c;包含一个浮点数&#xff0c;表示问题的解。 注意&#xff0c;结果保留 6 位小数。 数据范围 −10000≤…

Linux链接文件管理——硬链接、符号链接

Linux链接文件管理——硬链接、符号链接 硬链接 硬链接是对文件的另一个名字&#xff0c;对于硬链接的文件来说&#xff0c;他们拥有同样的inode号(索引节点号)&#xff0c;表示它们是同一个文件。 创建硬链接不会消耗新的inode和block。 删除硬链接文件&#xff0c;不会影响…

hadoop103: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

分析&#xff1a; 在启动hadoop服务的时候&#xff0c;遇到了这个问题&#xff1a; hadoop103: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password). 这个一看就是&#xff0c;密钥问题 于是ssh 主机名就行测试 需要输入密码&#xff0c;就说明这里有问…

【C++】每日一题 28 找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 #include <iostream> #include <string>…

C++笔记(函数重载)

目录 引入&#xff1a; 定义&#xff1a; 易错案例&#xff1a; 引入&#xff1a; 对于实现相似功能的函数&#xff0c;在命名时&#xff0c;我们常会出现命名重复的问题。对于C语言&#xff0c;编译器遇到这种命名重复的情况&#xff0c;会进行报错。而我们的C为了更方便程…

【计算机毕业设计】校园网书店系统——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

分布式锁-redission

5、分布式锁-redission 5.1 分布式锁-redission功能介绍 基于setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如HashTable这样的代码…

pycharm一直打不开

一直处在下面的页面&#xff0c;没有反应 第一种方案&#xff1a; 以管理员身份运行 cmd.exe&#xff1b;在打开的cmd窗口中&#xff0c;输入 netsh winsock reset &#xff0c;按回车键&#xff1b;重启电脑&#xff1b;重启后&#xff0c;双击pycharm图标就能打开了&#xf…

深度理解运放增益带宽积

原文来自微信公众号&#xff1a;工程师看海&#xff0c;与我联系&#xff1a;chunhou0820 看海原创视频教程&#xff1a;《运放秘籍》 大家好&#xff0c;我是工程师看海。 增益带宽积是运算放大器的重要参数之一&#xff0c;指的是运放的增益和带宽的乘积&#xff0c;这个乘积…

Mybatis-Plus使用方法

MyBatis-Plus 提供了丰富的增强版的 CRUD 方法&#xff0c;使得开发者能够更简洁、高效地进行数据库操作。以下是如何使用 MyBatis-Plus 自带的增强版 CRUD 方法的基本步骤&#xff1a; 添加依赖 首先&#xff0c;确保你的 Maven 项目中已经添加了 MyBatis-Plus 的相关依赖&a…

【S32K3 MCAL配置】-3.1-CANFD配置-经典CAN切换CANFD(基于MCAL+FreeRTOS)

"><--返回「Autosar_MCAL高阶配置」专栏主页--> 目录(共5页精讲,基于评估板: NXP S32K312EVB-Q172,手把手教你S32K3从入门到精通) 实现的架构:基于MCAL层 前期准备工作:

STC89C52学习笔记(四)

STC89C52学习笔记&#xff08;四&#xff09; 综述&#xff1a;本文讲述了在STC89C51中数码管、模块化编程、LCD1602的使用。 一、数码管 1.数码管显示原理 位选&#xff1a;对74HC138芯片的输入端的配置&#xff08;P22、P23、P24&#xff09;&#xff0c;来选择实现位选&…

玩转ChatGPT:Kimi测评(图片识别)

一、写在前面 ChatGPT作为一款领先的语言模型&#xff0c;其强大的语言理解和生成能力&#xff0c;让无数用户惊叹不已。然而&#xff0c;使用的高门槛往往让国内普通用户望而却步。 最近&#xff0c;一款由月之暗面科技有限公司开发的智能助手——Kimi&#xff0c;很火爆哦。…

【Keil5-编译4个阶段】

Keil5-编译 ■ GCC编译4个阶段■ 预处理->编译->汇编->链接■ GNU工具链开发流程图■ armcc/armasm&#xff08;编译C和汇编&#xff09;■ armlink &#xff08;链接&#xff09;■ armar &#xff08;打包&#xff09;■ fromelf &#xff08;格式转换器&#xff09…

C++17中的结构化绑定详解

C进阶专栏&#xff1a;http://t.csdnimg.cn/afM80 目录 1.什么是结构化绑定 2.结构化绑定的用法 2.1.对于数组 2.2.对于结构体 2.3.对于std::pair 2.4.对于std::tuple 2.5.对于std::array 2.6.对于类 2.7.结构化绑定与范围for循环 2.8.与const和引用结合使用 3.结构…

QTextStream的使用、技巧与注意事项

在Qt框架中&#xff0c;QTextStream类是进行文本数据读写操作的重要工具。无论是处理文本文件&#xff0c;还是在内存中操作字符串&#xff0c;QTextStream以其简洁易用的API和强大的功能&#xff0c;极大地简化了文本处理任务。本文将介绍QTextStream的使用方法、一些实用技巧…

【LAMMPS学习】八、基础知识(1.7) LAMMPS 与 MDI 库代码耦合

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

【并发】面试题汇总

并发篇1. 什么是线程和进程?2. 程序计数器为什么是私有的?3. 虚拟机栈和本地方法栈为什么是私有的?4. 如何理解线程安全和不安全&#xff1f;5. 线程的类型6. 如何创建线程&#xff1f;7. 线程的生命周期和状态8. 什么是死锁&#xff0c;死锁产生的条件9. 如何预防和避免线程…

LeetCode 刷题汇总——题目序号顺序版

剑指 Offer——和为 S 的两个数字 剑指 Offer——数字在排序数组中出现的次数 剑指 Offer——和为 S 的连续正数序列 剑指 Offer——最小的 K 个数 剑指 Offer——连续子数组的最大和 剑指 Offer——数组中的逆序对 LeetCode 1——两数之和 LeetCode 2——两数相加 LeetCode 3…