进程间通信 (匿名管道)

一、进程间通信的概念

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

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

        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。为了方便查看,顺便记录名字和pid
        string 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;   // 子进程的pid
    string _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。为了方便查看,顺便记录名字和pid
        string name = "process " + to_string(i);
        ChildPs.push_back(ChildP(pipefd[1], id, name));
        f_wfd.push_back(pipefd[1]);
    }
}

感谢大家观看!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CNN-Transformer时间序列预测

部分代码&#xff1a; # CNN-Transformer class CNNTransformerEncoder(nn.Module):def __init__(self, input_features, transformer_encoder_heads,embedding_features, cnn_kernel_size, dim_feedforward_enc, n_encoder_layer):super(CNNTransformerEncoder, self).__init…

分析染色体级别的基因组装配揭示了六倍体栽培菊花的起源和进化-文献精读-7

Analyses of a chromosome-scale genome assembly reveal the origin and evolution of cultivated chrysanthemum 分析染色体级别的基因组装配揭示了栽培菊花的起源和进化 六倍体植物基因组的文献&#xff0c;各位同仁还有什么有特色的基因组评论区留言~ 摘要 菊花&#xf…

spring boot —— Spring-Cloud-Zuul(网关服务getway),kafka笔记

一、 引入zuul依赖&#xff1a; org.springframework.cloud spring-cloud-starter-zuul 二、创建应用主类。使用EnableZuulProxy注解开启zuul的API网关服务功能&#xff1a; EnableZuulProxy SpringCloudApplication public class Application { public static void mai…

【漏洞复现】WordPress Welcart 任意文件读取漏洞(CVE-2022-4140)

0x01 产品简介 Welcart 是一款免费的 WordPress 电子商务插件。Welcart 具有许多用于制作在线商店的功能和自定义设置。您可以轻松创建自己的原始在线商店。 0x02 漏洞概述 Welcart存在任意文件读取漏洞&#xff0c;未授权的攻击者可以通过该漏洞读取任意文件&#xff0c;获…

【RAG实践】Rerank,让大模型 RAG 更近一步

RAGRerank原理 上一篇【RAG实践】基于LlamaIndex和Qwen1.5搭建基于本地知识库的问答机器人 我们介绍了什么是RAG&#xff0c;以及如何基于LLaMaIndex和Qwen1.5搭建基于本地知识库的问答机器人&#xff0c;原理图和步骤如下&#xff1a; 这里面主要包括包括三个基本步骤&#…

【无标题】系统思考—心智模式

“直到你使无意识变为有意识&#xff0c;它将指导你的生活并且你会称之为命运。”—卡尔荣格 心智模式深藏于我们内心之中&#xff0c;它潜移默化地影响着我们对世界的理解和判断。往往这些影响是如此隐蔽&#xff0c;以至于我们自己都未必察觉到是什么在驱动我们的选择、决策…

ES7-10:async和await、异步迭代..

1-ES7新特性 indexof如果没有就返回-1&#xff0c;有就返回索引 如果仅仅查找数据是否在数组中,建议使用includes,如果是查找数据的索引位置,建议使用indexOf更好一些 2-ES8-async和await 所有的需要异步处理的Promise对象都写在async中await等待结果 async、await 使异步操…

【MATLAB源码-第184期】基于matlab的FNN预测人民币美元汇率 输出预测图误差图RMSE R2 MAE MBE等指标

操作环境&#xff1a; MATLAB 2022a 1、算法描述 前馈神经网络&#xff08;Feedforward Neural Network, FNN&#xff09;是最简单也是应用最广泛的人工神经网络之一。在许多领域&#xff0c;尤其是数据预测方面&#xff0c;FNN已经展现出了卓越的性能和强大的适应性。 一、…

贪心算法|406.根据身高重建队列

力扣题目链接 class Solution { public:static bool cmp(const vector<int>& a, const vector<int>& b) {if (a[0] b[0]) return a[1] < b[1];return a[0] > b[0];}vector<vector<int>> reconstructQueue(vector<vector<int>…

鸿蒙应用开发之图案密码锁组件

前面学习了导航组件,现在来学习另一个密码设置和验证组件,这种组件比较常用。因为当用触屏手机之后,屏幕上就可以滑动操作,比普通PC电脑要多一些功能了。早前的密码都是输入数字,没有滑动输入九宫格的密码。 大体如下面的界面: 采用这种密码,一般情况下是不用记住数字,…

Vue - 你知道Vue中key的工作原理吗

难度级别:中级及以上 提问概率:80% 在Vue项目开发中,并不推荐使用索引做为key,以为key必须是唯一的,可以使用服务端下发的唯一ID值,也不推荐使用随机值做为key,因为如果每次渲染都监听到不一样的key,那么节点将无法复用,这与Vue节省…

Kotlin:常用标准库函数(let、run、with、apply、also)

一、let 扩展函数 Kotlin标准库函数let可用于范围确定和空检查。当调用对象时&#xff0c;let执行给定的代码块并返回其最后一个表达式的结果。对象可以通过引用(默认情况下)或自定义名称在块中访问。 let扩展函数源码 let.kt文件代码 fun main() {println("isEmpty $is…

企业如何防止内部人员泄密(实用方法分享)

“老干妈配方遭泄露”事件你们一定有所耳闻吧&#xff01; 2016年5月&#xff0c;“老干妈”工作人员发现&#xff0c;本地另一家食品加工企业生产的一款产品与老干妈品牌同款产品相似度极高。 经查&#xff0c;涉嫌窃取此类技术的企业从未涉足该领域&#xff0c;绝无此研发能…

C/S医学检验LIS实验室信息管理系统源码 医院LIS源码

LIS系统即实验室信息管理系统。LIS系统能实现临床检验信息化&#xff0c;检验科信息管理自动化。其主要功能是将检验科的实验仪器传出的检验数据经数据分析后&#xff0c;自动生成打印报告&#xff0c;通过网络存储在数据库中&#xff0c;使医生能够通过医生工作站方便、及时地…

基于Java+SpringBoot+Vue音乐网站(源码+文档+部署+讲解)

一.系统概述 随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;音乐管理展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;为…

[【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器

【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSON2WEB前端框架搭建 【J…

06 Php学习:字符串

PHP 中的字符串变量 在 PHP 中&#xff0c;字符串是一种常见的数据类型&#xff0c;用于存储文本数据。字符串变量可以包含字母、数字、符号等字符&#xff0c;并且可以进行各种操作和处理。以下是关于 PHP 中字符串变量的一些重要信息&#xff1a; 定义字符串变量&#xff1…

3 突破编程_前端_SVG(rect 矩形)

1 rect 元素的基本属性和用法 在SVG中&#xff0c;<rect> 元素用于创建矩形。 <rect> 元素有一些基本的属性&#xff0c;可以用来定义矩形的形状、位置、颜色等。以下是这些属性的详细解释&#xff1a; x 和 y &#xff1a;这两个属性定义矩形左上角的位置。 x …

Day:005 | Python爬虫:高效数据抓取的编程技术(爬虫效率)

爬虫之多线程-了解 单线程爬虫的问题 因为爬虫多为IO密集型的程序&#xff0c;而IO处理速度并不是很快&#xff0c;因此速度不会太快如果IO卡顿&#xff0c;直接影响速度 解决方案 考虑使用多线程、多进程 原理&#xff1a; 爬虫使用多线程来处理网络请求&#xff0c;使用线程…

服务器云主机进入黑洞了怎么解决

黑洞是指服务器受进犯流量超过本机房黑洞阈值时&#xff0c;云核算服务商屏蔽服务器的外网拜访。当服务器进入黑洞一段时间后&#xff0c;假如体系监控到进犯流量中止&#xff0c;黑洞会主动解封。 1&#xff1a;进入“黑洞”了&#xff0c;该怎么办&#xff1f; 由于黑洞是各…