进程间通信 (匿名管道)

一、进程间通信的概念

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

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

        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,一经查实,立即删除!

相关文章

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++笔记(函数重载)

目录 引入&#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;这个乘积…

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…

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

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

【现代C++】委托构造函数

现代C中的委托构造函数&#xff08;Delegating Constructors&#xff09;是C11引入的特性&#xff0c;它允许一个构造函数调用同一个类中的另一个构造函数&#xff0c;以避免代码重复。这种特性在初始化对象时提高了代码的复用性和清晰性。 1. 基本用法 在同一个类中&#xf…

(小红书平台)2024用户画像洞察报告

现今的小红书坐拥3亿月活用户&#xff0c;男女比例达到3:7&#xff0c;95后占比为50%&#xff0c;00后占比为35%&#xff0c;一二线城市用户占比50%。社区分享者超8000万&#xff0c;日均用户搜索渗透达到60%&#xff0c;UGC内容占比达90%。&#xff08;数据来源&#xff1a;小…

[lesson16]类的真正形态

类的真正形态 类的关键字 struct在C语言中以及有了自己的含义&#xff0c;必须继续兼容 在C中提供了新的关键字class用于类的定义 class和struct的用法是完全相同的 在用struct定义类时&#xff0c;所有成员的默认访问级别为public 在用class定义类时&#xff0c;所有成员…

虚拟网络设备的真正使命:实现有控制的通信

在数字化时代&#x1f4f2;&#xff0c;网络安全&#x1f512;成为了企业和个人防御体系中不可或缺的一部分。随着网络攻击的日益复杂和频繁&#x1f525;&#xff0c;传统的物理网络安全措施已经无法满足快速发展的需求。虚拟网络设备&#x1f5a7;&#xff0c;作为网络架构中…

谈谈功率IC巨头—士兰微

大家好&#xff0c;我是砖一。 今天给大家分享一下士兰微电子公司&#xff0c;&#xff0c;有做功率元器件&开关电源和IC的朋友可以了解一下&#xff0c;希望对你有用~ 1 公司介绍 士兰微电子成立于1997年&#xff0c;于2003年上市&#xff0c;总部位于杭州&#xff0c;…

unity按路径移动

using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class FollowPathMove : MonoBehaviour {public Transform[] wayPointArray;[SerializeField] private Transform PathA;//路径点的父物体[SerializeField]private Trans…

【春招面试篇】大厂面试干货分享

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多面经   &#x1f51d;&#x1f51d; 面试经验分享 1. 前言2. 去哪儿投简历?内推是否有用?3. 投递简历前的准备…

美团一面,面试官让介绍AQS原理并手写一个同步器,直接凉了

写在开头 今天在牛客上看到了一个帖子&#xff0c;一个网友吐槽美团一面上来就让手撕同步器&#xff0c;没整出来&#xff0c;结果面试直接凉凉。 就此联想到一周前写的一篇关于AQS知识点解析的博文&#xff0c;当时也曾埋下伏笔说后面会根据AQS的原理实现一个自定义的同步器…

【MySQL数据库 | 第二十五篇】深入探讨MVCC底层原理

前言&#xff1a; 在当今互联网时代&#xff0c;数据库扮演着数据存储和管理的关键角色。对于大型Web应用程序和企业级系统而言&#xff0c;高效地处理并发访问和事务管理是至关重要的。多版本并发控制&#xff08;MVCC&#xff09;是一种数据库事务处理的技术&#xff0c;旨…

【软件测试】个人博客系统测试

个人博客系统测试 一、项目背景1.1 技术背景1.2 功能背景 二、自动化测试2.1 什么是自动化测试2.2 通过使用selenium进行自动化测试的编写&#xff08;Java实现&#xff09;2.3 编写测试用例&#xff0c;执行自动化测试2.3.1 输入用户名:test,密码:123&#xff0c;登录成功2.3.…