1 进程间通信的必要性

首先要明确进程间是相互独立的(独享一份虚拟地址空间,页表,资源),那怎么样才能使得两个进程间实现资源的发送?所以,两个进程一定需要看到同一份资源,并且⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程间通信的目的:

1.数据传输

2.资源共享

3.通知事件(也就是⼀个进程向另⼀个或⼀组进程发送消息)

4.进程控制(一些进程希望完全控制另⼀个进程的执行)

1.1 进程间通信分类

管道:(基于文件的通信方法)

1.匿名管道pipe

2.命名管道

System V IPC:(单独设计的通信模块)

System V 消息队列 

System V 共享内存

System V 信号量

 POSIX IPC:(网络间进程通信)

消息队列

共享内存

信号量

互斥量

条件变量

读写锁

2 管道

管道是从⼀个进程连接到另⼀个进程的⼀个数据流。

管道的本质是一个基于文件系统的一个内存级的单向通信的文件,主要用于进程间通信(IPC)。

所以管道其实也是文件,在前面讲的文件系统中,那个管道文件是不是也要创建,要打开,要有对应的路径解析,要有对应的inode,那么文件使用的接口(write,read等管道也是可以直接使用的),但是这个文件不需要和磁盘进行IO,只需要存在内核中,所以就没有路径,没有文件名,是内核中模拟的一个文件(所以叫匿名管道),创建一个缓冲区,利用缓冲区实现两个进程看到同一份资源。

3 匿名管道

系统调用:

int pipe(int fd[2]);

这里fd是一个输出型参数,fd所对应的也就是之前讲的文件描述符,fd[0]表示读端, fd[1]表示写端,

成功返回0,失败返回错误代码。

3.1 用fork来共享管道原理

父进程创建管道后,创建子进程,子进程会拷贝父进程的资源,但是文件本身并未拷贝,而是访问该文件的文件描述符,所对应的地址是同一个文件,这时父进程关闭读端,子进程关闭写端,(这里关闭读写端的操作可以不做也能使用,但是还是最好做,以防误操作,毕竟是要实现单向通信)这样父进程就可以向管道中写入,子进程就可以从管道中读取文件。

大致逻辑:

给出一个代码例子:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[])
{int pipefd[2];if (pipe(pipefd) == -1){perror("pipe");}pid_t pid;pid = fork();if (pid == -1){perror("fork");}  if (pid == 0) {close(pipefd[0]);write(pipefd[1], "hello", 5);close(pipefd[1]);exit(0);}close(pipefd[1]);char buf[10] = {0};read(pipefd[0], buf, 10);printf("buf=%s\n", buf);return 0;
}

这里就是通过子进程向管道写入“hello”,父进程读取并打印。

3.2 管道读写规则

当没有数据可读时:

O_NONBLOCK disable:read调⽤阻塞,即进程暂停执行,⼀直等到有数据来到为止。

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道满的时候 :

O_NONBLOCK disable:write调⽤阻塞,直到有进程读⾛数据 

O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致 write进程退出。

3.3 管道读写的几种情况

情况一:管道里没有数据,读端正常

这时读端就会阻塞,等待写端写入。

情况二:读端不读取,写端一直写

缓冲区写满了,就不会再写入了。

情况三:写端关闭(不写),读端正常

read之后,就会返回0,表示读到文件结尾。

情况四:读端关闭,写端正常

OS直接杀掉该进程。(读端关闭了,写入就没有意义了,OS不会做浪费时间和空间的事,所以直接杀掉该进程)

3.4 管道的特点

只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,⼀个管道由⼀个进 程创建,然后该进程调用fork,此后父,子进程之间就可应用该管道。

管道提供流式服务

进程退出,管道释放,所以管道的生命周期随进程

内核会对管道操作进行同步与互斥

管道是半双工的,数据只能向⼀个方向流动;需要双方通信时,需要建立起两个管道

3.5 创建进程池处理任务

进程池是用父进程创建一批子进程,通过父进程向管道里面写入信息通知对应的进程完成对应的任务。

创建函数:

using callback_t =std::function<void(int fd)>;
bool InitProcessPool(callback_t cb){for (int i = 0; i < _processnum; i++){int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0){perror("pipe");return false;}pid_t id = fork();if (id < 0){perror("fork");return false;}else if (id == 0){close(pipefd[1]);cb(pipefd[0]);exit(0);}close(pipefd[0]);std::string name = "channel-" + std::to_string(i);_channels.emplace_back(pipefd[1], name, id);}return true;}

通过一个数组将这一批子进程(管道写端)进行(channel)类的管理,这里的cb是父进程通知子进程后的处理对应任务的函数。

channel类:

class channel
{
public:channel(){}channel(int wfd, std::string name, pid_t id): _wfd(wfd), _name(name), _sub_target(id){}int fd() { return _wfd; }std::string name() { return _name; }pid_t target() { return _sub_target; }void Close(){close(_wfd);}void Wait(){pid_t rid = waitpid(_sub_target, nullptr, 0);(void)rid;}~channel(){}
private:int _wfd;std::string _name;pid_t _sub_target;
};

成员包括写端的文件描述符,管道名称,对应pid。以及关闭写端的函数,回收子进程的函数。

任务处理函数:

这里通过一批打印函数来模拟任务的调用

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <functional>
// 4种任务
// task_t[4];using task_t = std::function<void()>;void Download()
{std::cout << "我是一个downlowd任务" << std::endl;
}void MySql()
{std::cout << "我是一个 MySQL 任务" << std::endl;
}void Sync()
{std::cout << "我是一个数据刷新同步的任务" << std::endl;
}void Log()
{std::cout << "我是一个日志保存任务" << std::endl;
}std::vector<task_t> tasks;class Init
{
public:Init(){tasks.push_back(Download);tasks.push_back(MySql);tasks.push_back(Sync);tasks.push_back(Log);}
};Init ginit;
while(true){int code = 0;//std::cout << "子进程阻塞: " << getpid() << std::endl;ssize_t n = read(fd, &code, sizeof(code));if(n == sizeof(code)) // 任务码{std::cout << "子进程被唤醒: " << getpid() << std::endl;if(code >= 0 && code < tasks.size()){tasks[code]();}else{std::cerr << "父进程给我的任务码是不对的: " << code << std::endl;}}else if(n == 0){std::cout << "子进程应该退出了: " << getpid() << std::endl;break;}else{std::cerr << "read fd: " << fd << ", error" << std::endl;break;}}

读取成功进行对应任务处理,为0说明写端关闭了,子进程可以退出了。

控制发送任务的函数:

void PollingCtrlSubProcess(int count){if (count < 0)return;int index = 0;while (count){CtrlSubProcessHelper(index);count--;}}void CtrlSubProcessHelper(int &index){// 1. 选择一个通道(进程)int who = index;index+=rand();index %= _channels.size();// 2. 选择一个任务,随机int x = rand() % tasks.size(); // [0, 3]// 3. 任务推送给子进程std::cout << "选择信道: " << _channels[who].name() << ", subtarget : " <<                                      _channels[who].target() << std::endl;write(_channels[who].fd(), &x, sizeof(x));sleep(1);}

count表示需要处理的任务个数。

回收函数和回收子进程:

 void WaitSubProcesses(){for (auto &c : _channels){c.Close();c.Wait();}}

注意这里关闭管道是并没有关闭完的,是有问题的?

注意创建子进程时,会拷贝父进程的资源,创建第一个子进程时,父进程关闭读端,第一个子进程会关闭写端,再创建一个新的子进程,会继承父进程的写端,子进程关闭对应的写端(但是关闭的是第二次创建管道的写端,而第一次的写端被这个新的子进程继承下来了),所以往后再创建子进程,也会发送同样的问题,所以需要再每次子进程关闭写端时,需要将之前继承下来的写端也关闭掉。

加入这段代码:

for(auto&e:_channels)
{std::cout  << e.fd() << " ";e.Close();
}

这里也是对进程池进行了封装:

processpool.hpp

#pragma once
#include"task.hpp"
#include <iostream>
#include<functional>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sys/wait.h>
#include <vector>const int gprocess_num = 5;
using callback_t =std::function<void(int fd)>;class channel
{
public:channel(){}channel(int wfd, std::string name, pid_t id): _wfd(wfd), _name(name), _sub_target(id){}int fd() { return _wfd; }std::string name() { return _name; }pid_t target() { return _sub_target; }void Close(){close(_wfd);}void Wait(){pid_t rid = waitpid(_sub_target, nullptr, 0);(void)rid;}~channel(){}
private:int _wfd;std::string _name;pid_t _sub_target;
};class Processpool
{
private:void CtrlSubProcessHelper(int &index){// 1. 选择一个通道(进程)int who = index;index++;index %= _channels.size();// 2. 选择一个任务,随机int x = rand() % tasks.size(); // [0, 3]// 3. 任务推送给子进程std::cout << "选择信道: " << _channels[who].name() << ", subtarget : " << _channels[who].target() << std::endl;write(_channels[who].fd(), &x, sizeof(x));sleep(1);}
public:Processpool(int num=gprocess_num) : _processnum(num){srand(time(0));}bool InitProcessPool(callback_t cb){for (int i = 0; i < _processnum; i++){int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0){perror("pipe");return false;}pid_t id = fork();if (id < 0){perror("fork");return false;}else if (id == 0){std::cout << "进程:" << getpid() << ", 关闭了: ";for(auto&e:_channels)//处理关闭之前的读端{std::cout  << e.fd() << " ";e.Close();}std::cout<<std::endl;close(pipefd[1]);cb(pipefd[0]);exit(0);}sleep(1);close(pipefd[0]);std::string name = "channel-" + std::to_string(i);_channels.emplace_back(pipefd[1], name, id);}return true;}void PollingCtrlSubProcess(){int index = 0;while (true){CtrlSubProcessHelper(index);}}void PollingCtrlSubProcess(int count){if (count < 0)return;int index = 0;while (count){CtrlSubProcessHelper(index);count--;}}void WaitSubProcesses(){for (auto &c : _channels){c.Close();c.Wait();}}
private:std::vector<channel> _channels;int _processnum;
};

main.cc

#include"processpool.hpp"
#include<vector>int main()
{Processpool pool(5);pool.InitProcessPool([](int fd){while(true){int code = 0;//std::cout << "子进程阻塞: " << getpid() << std::endl;ssize_t n = read(fd, &code, sizeof(code));if(n == sizeof(code)) // 任务码{std::cout << "子进程被唤醒: " << getpid() << std::endl;if(code >= 0 && code < tasks.size()){tasks[code]();}else{std::cerr << "父进程给我的任务码是不对的: " << code << std::endl;}}else if(n == 0){std::cout << "子进程应该退出了: " << getpid() << std::endl;break;}else{std::cerr << "read fd: " << fd << ", error" << std::endl;break;}}});sleep(5);// 3. 控制进程池pool.PollingCtrlSubProcess(10);// 4. 结束线程池pool.WaitSubProcesses();std::cout << "父进程控制子进程完成,父进程结束" << std::endl;}

task.hpp

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <functional>
// 4种任务
// task_t[4];using task_t = std::function<void()>;void Download()
{std::cout << "我是一个downlowd任务" << std::endl;
}void MySql()
{std::cout << "我是一个 MySQL 任务" << std::endl;
}void Sync()
{std::cout << "我是一个数据刷新同步的任务" << std::endl;
}void Log()
{std::cout << "我是一个日志保存任务" << std::endl;
}std::vector<task_t> tasks;class Init
{
public:Init(){tasks.push_back(Download);tasks.push_back(MySql);tasks.push_back(Sync);tasks.push_back(Log);}
};Init ginit;

运行结果:

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

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

相关文章

CAN2.0、DoIP、CAN-FD汽车协议详解与应用

一、CAN2.0 协议详解与应用示例 1. 技术原理与特性 协议架构&#xff1a;基于 ISO 11898 标准&#xff0c;采用载波监听多路访问 / 冲突检测&#xff08;CSMA/CD&#xff09;机制&#xff0c;支持 11 位&#xff08;CAN2.0A&#xff09;或 29 位&#xff08;CAN2.0B&#xff…

使用nvm管理npm和pnpm

1.使用nvm管理npm // 查看nvm版本 nvm -v // 查看可安装的 node 版本 nvm ls-remote // 安装指定 node 版本 nvm install 24.0.0 // 查看当前已安装的 node 版本及当前使用的版本 nvm list // 使用某个版本 node nvm use 24.0.0 // 卸载指定 node 版本 nvm uninstall 16.20.1…

YOLO11+QT6+Opencv+C++训练加载模型全过程讲解

实现效果&#xff1a; Yolov11环境搭建&#xff08;搭建好的可以直接跳过&#xff09; 最好使用Anconda进行包管理&#xff0c;安装可参考【文章】。下面简单过一下如何快速部署环境。如果搭建过或可以参考其他文章可以跳过Yolo11环境搭建这一章节。总体来说Yolov11环境搭建越…

Python 脚本,用于将 PDF 文件高质量地转换为 PNG 图像

import os import fitz # PyMuPDF from PIL import Image import argparse import logging from tqdm import tqdm# 配置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(PDF2PNG)def convert_pdf_…

【CUDA GPU 支持安装全攻略】PyTorch 深度学习开发者指南

PyTorch 的 CUDA GPU 支持 安装五条铁律&#xff08;最新版 2025 修订&#xff09;&#xff08;适用于所有用户&#xff09;-CSDN博客 是否需要预先安装 CUDA Toolkit&#xff1f;——按使用场景分级推荐及进阶说明-CSDN博客 “100% 成功的 PyTorch CUDA GPU 支持” 安装攻略…

Cyberith 运动模拟器Virtualizer2:提升虚拟现实沉浸体验

奥地利Cyberith公司是一家专注于虚拟现实&#xff08;VR&#xff09;互动解决方案的创新型科技企业&#xff0c;以其研发的Virtualizer虚拟现实步态模拟设备而闻名。该公司的核心技术体现在其设计和制造的全方位跑步机式VR交互平台上&#xff0c;使得用户能够在虚拟环境中实现自…

常见的数据处理方法有哪些?ETL中的数据处理怎么完成

在数字化转型纵深推进的背景下&#xff0c;数据作为新型生产要素已成为驱动企业战略决策、科研创新及智能化运营的核心战略资产。数据治理价值链中的处理环节作为关键价值节点&#xff0c;其本质是通过系统化处理流程将原始观测数据转化为结构化知识产物&#xff0c;以支撑预测…

WHAT - 为甲方做一个官网(二)- 快速版

文章目录 一、明确需求优先级&#xff08;快速决策&#xff09;二、推荐零代码/低代码工具&#xff08;附对比&#xff09;方案1&#xff1a;低代码建站平台&#xff08;适合无技术用户&#xff0c;拖拽式操作&#xff09;方案2&#xff1a;CMS系统&#xff08;适合内容更新频繁…

音视频之H.264视频编码传输及其在移动通信中的应用

系列文章&#xff1a; 1、音视频之视频压缩技术及数字视频综述 2、音视频之视频压缩编码的基本原理 3、音视频之H.264/AVC编码器原理 4、音视频之H.264的句法和语义 5、音视频之H.264/AVC解码器的原理和实现 6、音视频之H.264视频编码传输及其在移动通信中的应用 7、音视…

C#语言入门-task2 :C# 语言的基本语法结构

下面从四个方面对C#的基本语法进行简单介绍&#xff1a; 1. 数据类型 C#的类型可分为值类型和引用类型。值类型变量直接存储数据&#xff0c;引用类型变量则存储对象的引用。 值类型&#xff1a;涵盖整数类型&#xff08;像int、long&#xff09;、浮点类型&#xff08;例如…

c#笔记之类的常量、字段和属性

学习内容: 一、字段 字段是为了对象或者类型存储数据的,可以表达一个对象或者类型的状态;也叫做成员变量;注意字段是在类里面声明的;在方法里声明的是局部变量; 1.1实例字段 用来表示每个实例的状态;比如一个students类;要了解一个学生一般看名字和成绩;所以名字和…

Linux 常用命令(入门)

Linux 常用命令 一、Linux 命令基础 (一)命令格式 Linux 命令的一般格式为:command [-options] [parameter1] … 。其中,command 是命令名,通常是相应功能的英文单词或其缩写;[-options] 是选项,用于对命令进行控制,可省略;parameter1 … 是传给命令的参数,可以是…

CppCon 2016 学习:Parallelism in Modern C++

这段介绍的是 HPX (High Performance ParalleX)&#xff0c;一个现代C的通用并行运行时系统&#xff0c;重点包括&#xff1a; 通用性&#xff1a;适用于各种规模的应用&#xff0c;从小型到超大规模分布式系统。统一标准API&#xff1a;符合C标准&#xff0c;方便编写异步、并…

机器学习监督学习实战七:文本卷积神经网络TextCNN对中文短文本分类(15类)

本文介绍了一个基于TextCNN模型的文本分类项目&#xff0c;使用今日头条新闻数据集进行训练和评估。项目包括数据获取、预处理、模型训练、评估测试等环节。数据预处理涉及清洗文本、中文分词、去除停用词、构建词汇表和向量化等步骤。TextCNN模型通过卷积层和池化层提取文本特…

iot-dc3 项目Bug修复保姆喂奶级教程

一.Uncaught (in promise) ReferenceError: TinyArea is not defined 1.触发场景 前端设备模块,点击关联模板、关联位号、设备数据,无反应,一直切不过去,没有报错通知,F12查看控制台报错如下: 2.引起原因 前端导入的库为"@antv/g2": "^5.3.0",在 P…

Spring Boot + MyBatis Plus + SpringAI + Vue 毕设项目开发全解析(源码)

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 Spring Boot MyBatis Plus SpringAI Vue 毕设项目开发全解析 目录 一、项目概述与技术选型 项目背景与需求分析技术栈选择…

Vitess数据库部署与运维深度指南:构建可伸缩、高可用与安全的云原生数据库

摘要 Vitess是一个为MySQL和MariaDB设计的云原生、水平可伸缩的分布式数据库系统&#xff0c;它通过分片&#xff08;sharding&#xff09;实现无限扩展&#xff0c;同时保持对应用程序的透明性&#xff0c;使其无需感知底层数据分布。该项目于2019年从云原生计算基金会&#…

SpringAI+DeepSeek大模型应用开发——6基于MongDB持久化对话

持久化对话 默认情况下&#xff0c;聊天记忆存储在内存中ChatMemory chatMemory new InMemoryChatMemory()。 如果需要持久化存储&#xff0c;可以实现一个自定义的聊天记忆存储类&#xff0c;以便将聊天消息存储在你选择的任何持久化存储介质中。 MongoDB 文档型数据库&…

Mac电脑-音视频剪辑编辑-Final Cut Pro X(fcpx)

Final Cut Pro Mac是一款专业的视频剪辑工具&#xff0c;专为苹果用户设计。 它具备强大的视频剪辑、音轨、图形特效和调色功能&#xff0c;支持整片输出&#xff0c;提升创作效率。 经过Apple芯片优化&#xff0c;利用Metal引擎动力&#xff0c;可处理更复杂的项目&#xff…

不同程度多径效应影响下的无线通信网络电磁信号仿真数据生成程序

生成.mat数据&#xff1a; %创建时间&#xff1a;2025年6月19日 %zhouzhichao %遍历生成不同程度多径效应影响的无线通信网络拓扑推理数据用于测试close all clearsnr 40; n 30;dataset_n 100;for bias 0.1:0.1:0.9nodes_P ones(n,1);Sampling_M 3000;%获取一帧信号及对…