进程间通信基础知识【Linux】——上篇

目录

一,理解进程之间的通信

1. 进程间通信目的

2. 进程间通信的技术背景

3,常见的进程间通信

二,管道

1. 尝试建立一个管道

管道的特点:

管道提供的访问控制:

2. 扩展:进程池

阶段一: 创建多个子进程

阶段二:构建命令方法

ProcessPool.cpp

task.hpp

下一期:进程通信基础知识

结语


一,理解进程之间的通信

首先,系统在设计时,秉持这相互独立的原则,因此要想实现进程之间的通信是比较困难的。而进程之间的通信本质上是:不同的进程能访问同一份数据

1. 进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。实现多进程协同工作,比如:A进程完成,数据收集整合;B进程接受A进程结果,分析问题。

2. 进程间通信的技术背景

1)进程是具有独立性的,虚拟地址 + 页表 保证进程之间的独立性 (内核中数据结构 , 代码以及数据)

2)通信成本比较高。

3,常见的进程间通信

1. Linux系统——管道

2. SystemV——单机通信(多进程)

3. posix——网络通信

二,管道

什么是管道
管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道。

而这个 “ 管道 ”虽然是一个文件,但它不向硬盘写入,管道里的数据是内存级的临时数据

fd 这个变量就是我们父进程创建的那个pipefile(管道文件描述符存储数组),在经过pipe创建后,将管道的文件描述符填入fd数组中。

1. 尝试建立一个管道

#include <iostream>
#include <unistd.h>
#include <string>
#include <assert.h>
#include <sys/types.h>
using namespace  std;
int main()
{
    // 用数组记录,读写端
	int pipefd[2] = {0}; // pipefd[0],是读端,0就如一张口;pipefd[1],是写端,1就如一只笔。
    int ret = pipe(pipefd);
	assert(ret != -1);
	pid_t pd = fork();

	// 假设父进程需要:写。
	if (pd == 0)
	{  // 子进程创建成功
	   // 子进程则需要的是:读
	   close(pipefd[1]);
	   cout << "chail success" << endl;
	   char red[1024];
       // 循环接收并打印数据
       while (1)
	   {
		  read(pipefd[0], red, sizeof (red) -1);
		  cout << red << "### " << "我是子进程,pid:" << getpid() << "父进程: " << getppid() << endl;
	   }
       exit(1);
	}

    // 父进程, 功能:写
	close(pipefd[0]);
    string base_str = "我是父进程, 正在给你发消息:";
	char buffer[1024];   // 输出缓冲区
	int count = 0;
	while (1)  // 不断地向子进程发送数据
	{
		// 向缓冲区里,不断输入变换的消息
		int len = snprintf(buffer, sizeof buffer, "%s pid = [%d], 消息数量: %d", base_str.c_str(), getpid(), count++);
		// 向管道写入
		write(pipefd[1], buffer, len);
		sleep(1); // 一秒秒的刷新
	} 
	return 0;
}

发现现象: 

我们是否注意到,在父进程写数据时,是每隔一秒写一份到管道,而我们没有限制子进程打印的间隔,但从结果来看,向管道内取数据(子进程打印过程)似乎受到了什么控制

我们尝试将父进程写入间隔设置为默认0,将子进程的读出时间设置为10秒一次读。结果上,我们能看到父进程在写入到一定次数后,停止了写入;子进程经过10秒后,会打印一大堆数据,直到打印完为止。从这个现象我们可以发现:

管道提供访问控制。管道本质上也是一个文件,那么也有其最大容量,当即将满时,暂停写入,等待空间当管道为空时,读操作将进行等待,等待数据写入

管道的特点:


1. 管道是用来父子关系的通信管道,是具有继承性的。

2. 管道可以提供进程间通信提供访问控制

3. 管道提供面向流试通信服务(字节流——需要协议进行数据区分,后面我们再聊)。

4. 管道是基于文件的,文件的生命周期是随进程的,因此管道的生命周期也是随进程的。

5. 管道是单向通信,属于半双工通信的一种(半双工意思是永远只有一方在写,一方在读;全双工则可以双方在写,双方在读)

管道提供的访问控制:

2. 扩展:进程池

相关知识:

1.)unin32_t  类型

uint32_t是一个无符号32位整数类型,其意义在于表示一个无符号的32位整数,范围为0到4294967295。使用uint32_t类型可以确保数据的范围和位数符合要求,同时提高代码的可移植性和可读性。这样不管在任何一种型号的机器上sizeof取得的字节数都是4字节。ssize_t类型表示有符号的大小类型,通常用于表示某些系统调用的返回值或参数。它的大小通常与机器的字长相同,即32位机器上为4字节,64位机器上为8字节。ssize_t类型通常用于表示读取或写入的字节数

2.)functiaonal 头文件

头文件 functional 是 C++ 标准库中的一个头文件,它包含了一些模板类函数对象,用于支持函数式编程和泛型编程。这些类和函数对象可以帮助程序员编写更加灵活和抽象的代码,提高代码的可读性和可维护性。它还提供了一些算法和函数,如 std::function、std::bind、std::mem_fn 等,用于处理函数对象和函数指针,以及实现函数的组合、绑定和适配等功能。

因此,头文件 functional 的意义在于为 C++ 程序员提供了一些强大的工具,帮助他们更好地利用函数式编程和泛型编程的特性,提高代码的质量和效率。

首先什么是进程池??

通俗的理解为,子进程替父进程进行数据处理

父进程为了在主程序中完成任务,需要让子进程去处理数据,整理,整合数据,但为了提高创建父进程的效率,在父进程还未向子进程派发任务前,提前创建多个子进程,然后通过管道进行命令传递

阶段一: 创建多个子进程

通过for循环创建多个子进程,并通过管道联系!

阶段二:构建命令方法

我们将方法单独一个文件向管道中读取命令信息,然后根据命令信息调用对应的方法

ProcessPool.cpp

#include <iostream>
#include <assert.h>
#include <vector>
#include <unistd.h>
#include "Task.hpp"    //导入的方法文件
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace  std;

#define NAM_OF_PRO 5

// 对命令进行处理
int waitcommand(int fd, bool& quit)
{
   uint32_t command = 0; // 取个32位的类型,用来获取4字节的数据
   ssize_t byte = read(fd,  &command,  sizeof command);
   // 如果父进程没有发送  
   if (byte == 0)  
   {
      quit = true;  // 命令管道开启  //你将他开启了
      return 0;
   }
  // 检测是否是4字节的命令消息1
   assert(byte == sizeof (uint32_t));
   return command;
}

 int main()
 { 

   // 阶段一 ; 创建多个子进程
   // 放pid 与 创建时所在的端口——fd
   vector<pair<pid_t, int>> slots; // slots 位置的意思 
   // 创建多个进程
   for (int i = 0; i < NAM_OF_PRO; i++)
   {
    // 建立管道
    int pipefil[2] = {0};
    int ret = pipe(pipefil);
    assert(ret != -1); // 管道检测

    // 创建子进程
    pid_t fd = fork();
     if (fd == 0)  // 子进程
    {
      // 关闭写端
      close(pipefil[1]);
       while (1)
       {   
         // 处理子进程任务
         //未接收数据,子进程进入阻塞状态
         // 阶段二:为子进程设置任务
         bool quit  = false;   // 管道是否被使用
         int k = waitcommand(pipefil[0], quit);  // 进入等待命令函数
         // cout << "K:" << k << " quit " << quit  << endl;
         if (quit)
            break;  // 目的:当我们开始关闭管道时,read()会读取0字节,这时让子进程退出

         tasklist[k]();  // 利用函数容器直接调用,方法函数
       } 
       exit(1);
    }
   
   // 父进程,关闭读端
   close(pipefil[0]);
   // 同时记录子进程,pid数据
   slots.push_back({fd, pipefil[1]});
   }
   
   // 阶段二: 父进程向子进程传递命令信息
   // 要想达到子进程接受命令较合理——负载均衡。
   srand((unsigned)time(0) ^ getpid() * 233333);  // 位运算与相乘2333333,目的是:让数据更随机
   uint32_t command = 0;

   Taskloading();  // 对方法进行加载
   while (true)
   {
   cout << "###########################################################\n";
   cout << "##     0. 退出  1. 查看方法表      2. 输入命令  #############\n";
   cout << "###########################################################\n";
   cout <<  "请输入:";
   cin >> command;

	//你往管道里面写数据的逻辑在哪里,定位到那里先
   //子进程正常执行了,你的问题是什么    
    if (command ==  1 )
    {
      print_task();
      // continue;
    } else if ( command == 2)
    {
      cout << "Please imput command : ";
      cin >> command;
      if (!(command >= 0 && command < tasklist.size()))
      {
         cout << "无效命令" << endl;
         continue;
      }
      size_t n = rand() % slots.size();  // 随机数选择子进程
      ssize_t ret = write(slots[n].second, &command, sizeof command);
      if ( ret != sizeof command)
      {
         cout << " write  fail " << endl;
      }else
      {
         cout << " write success " << "  child pid:[" << slots[n].first << "] " << "执行:"
         << command << " " << task_inf[command]<< endl;
      }

    }else if (command == 0)
    {
      cout << "退出成功" << endl;
      break;
    }
    else
    {
       cout << "无效命令,请重新输入" << endl;
    }

    sleep(1);
   }

   // 完成分配后,关闭管道
   for (const auto& e : slots)
    {
        close(e.second);
    }

   // 回收子进程
   for (const auto& e : slots)
   {
       waitpid(e.first, nullptr, 0); // 由于auto只遍历一次,所以不能用轮询回收
   }

    return 0;
 }

task.hpp

#pragma once
#include <iostream>
#include <assert.h>
#include <vector> 
#include <string>
#include <unordered_map>
#include <functional>
#include <unistd.h>

using namespace std;

typedef function<void()> func;

vector<func> tasklist;   // 记录方法数
unordered_map<int, string> task_inf;  // 记录方法信息。

void readMySQL()
{
    cout << " process : " << getpid() << " 访问数据库";
}

void val()
{
    cout << " process : " << getpid() << " 进行数据运算";
}


void save()
{
    cout << " process : " << getpid() << " 进行数据持久化";
}

void cal()
{
    cout << " process : " << getpid() << " 进行数据加密";
}

// 加载任务表
void Taskloading()
{
    task_inf.insert({tasklist.size(), "readMySQL"});
    tasklist.push_back(readMySQL);

    task_inf.insert({tasklist.size(), "val"});
    tasklist.push_back(val);
    
    task_inf.insert({tasklist.size(), "save"});
    tasklist.push_back(save);
    
    task_inf.insert({tasklist.size(), "cal"});
    tasklist.push_back(cal);

    cout << "方法加载成功!" << endl;
}

// 打印方法表
void print_task()
{

    for (const auto& e : task_inf)
    {
        cout << e.first << " " << e.second << endl;
    }
}

int tasksize()
{
    return tasklist.size();
}

我给大家留了个坑,不知道大家有没有发现呢? 我们在最后的时候调用不了函数

解析: 是进程的独立性导致的。  我们知道我们在已经对方法容器进行了加载,但是在fork之后父进程的加载,父进程进行了实时拷贝,子进程的tasklist任然是未加载状态,这时你就会问了,既然子进程访问的是一个空容器,那我们不就是非法访问了吗?? 是的,我们没有看到报错是因为我们在父进程,子进程的报错不会导致父进程退出,我们通过 查看进程状态表即可发现,由于子进程的非法访问,导致子进程崩溃,成为了僵尸进程

解决方法:

1, fork前,进行方法表加载。

2.   每个子进程都加载。

分析:父子进程,对共享的全局变量,都只是只读的权限,修改则要写实拷贝,因此,要么全局变量在fork之前就数据加载好;要么,在每个子进程都加载一份方法表。如果是我,我会一开始就加载好,这样就只要保存一份数据。

下一期:进程通信基础知识

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

【实验】配置用户自动获取IPv6地址的案例

热门IT课程-试听视频文章浏览阅读49次。认证课程介绍&#xff1a;华为HCIA试听课程 &#xff1a; 华为HCIA试听课程&#xff1a;华为HCIA试听课程&#xff1a;华为HCIP试听课程&#xff1a;思科CCNA试听课程&#xff1a;思科CCNA试听课程&#xff1a;思科CCNA试听课程&#xff…

回归预测 | MATLAB实现基于LightGBM算法的数据回归预测(多指标,多图)

回归预测 | MATLAB实现基于LightGBM算法的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现基于LightGBM算法的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLA…

高效办公:如何使用视频剪辑工具批量转码,mp4视频到TS视频

在视频处理过程中&#xff0c;转码是一项常见的任务。将MP4视频转换为TS视频可以提供许多优势&#xff0c;包括更好的兼容性、更广泛的设备和平台支持以及更高的视频质量。然而&#xff0c;手动转码大量视频文件可能会非常耗时且效率低下。为了实现高效办公&#xff0c;可以使用…

内存函数​(memcpy、memmove、memset、memcmp)

目录 一、memcpy的使用和实现 使用&#xff1a; 模拟实现&#xff1a; 二、memmove 使用和模拟实现 模拟实现&#xff1a; 2.1难点&#xff1a; 覆盖拷贝所在的问题 memset的使用 memcmp的函数的使用​ 一、memcpy的使用和实现 memcpy 拷贝的就是不重叠的内存。 参数…

webpack如何处理浏览器的样式兼容问题postcss

一、准备工作 css/index.css添加样式 .word {color: red;user-select: none; } 为了兼容不同的浏览器我们需要添加前缀比如&#xff1a; -webkit-user-select: none; 这个工作可以通过postcss的插件postcss-preset-env处理 二、安装依赖 pnpm i -D postcss postcss-loader…

TCP 连接建立

1&#xff1a;TCP 三次握手过程是怎样的&#xff1f; 客户端和服务端都处于 CLOSE 状态&#xff0c;服务端主动监听某个端口&#xff0c;处于 LISTEN 状态 第一次握手&#xff1a;客户端带着序号和SYN为1&#xff0c;把第一个 SYN 报文发送给服务端&#xff0c;客户端处于 SYN-…

C库函数—sprintf

函数介绍&#xff1a; C 库函数 int sprintf(char *str, const char *format, ...) 发送格式化输出到 str 所指向的字符串。 参数&#xff1a; str -- 这是指向一个字符数组的指针&#xff0c;该数组存储了 C 字符串。format -- 这是字符串&#xff0c;包含了要被写入到字符串 …

[架构之路-254]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 全程概述

目录 一、软件架构概述 1.1 什么是软件架构 1.2 为什么需要软件架构设计 1.3 软件架构设计在软件设计中位置 &#xff08;1&#xff09;软件架构设计&#xff08;层次划分、模块划分、职责分工&#xff09;&#xff1a; &#xff08;2&#xff09;软件高层设计、概要设计…

JVM执行引擎以及调优

1.JVM内部的优化逻辑 1.1JVM的执行引擎 javac编译器将Person.java源码文件编译成class文件[我们把这里的编译称为前期编译]&#xff0c;交给JVM运行&#xff0c;因为JVM只能认识class字节码文件。同时在不同的操作系统上安装对应版本的JDK&#xff0c;里面包含了各自屏蔽操作…

LeetCode(36)旋转图像【矩阵】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 48. 旋转图像 1.题目 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在** 原地** 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 …

【数据挖掘】国科大刘莹老师数据挖掘课程作业 —— 第二次作业

Written Part 1. 给定包含属性&#xff5b;Height, Hair, Eye&#xff5d;和两个类别&#xff5b;C1, C2&#xff5d;的数据集。构建基于信息增益&#xff08;info gain&#xff09;的决策树。 HeightHairEyeClass1TallBlondBrownC12TallDarkBlueC13TallDarkBrownC14ShortDark…

基于Qt QChart和QChartView实现正弦、余弦、正切图表

# 源码地址 https://gitcode.com/m0_45463480/QChartView/tree/main# .pro QT += charts​​HEADERS += \ chart.h \ chartview.h​​SOURCES += \ main.cpp \ chart.cpp \ chartview.cpp​​target.path = $$[QT_INSTALL_EXAMPLES]/charts/zoomlinechartINSTAL…

用customize-cra+react-app-rewired配置less+css module

1. 安装 npm i less less-loader -D npm i customize-cra-less-loader -D2.添加配置项 //config-overrides.js const { override } require(customize-cra); const addLessLoader require("customize-cra-less-loader");module.exports {webpack: override(addL…

蓝桥杯每日一题2023.11.29

题目描述 #include <stdio.h> #include <string.h>void StringInGrid(int width, int height, const char* s) {int i,k;char buf[1000];strcpy(buf, s);if(strlen(s)>width-2) buf[width-2]0;printf("");for(i0;i<width-2;i) printf("-"…

三次握手和四次挥手

TCP 协议简述 TCP 提供面向有连接的通信传输&#xff0c;面向有连接是指在传送数据之前必须先建立连接&#xff0c;数据传送完成后要释放连接。 无论哪一方向另一方发送数据之前&#xff0c;都必须先在双方之间建立一条连接。在TCP/IP协议中&#xff0c;TCP协议提供可靠的连接…

洛谷100题DAY8

36.P1416 攻击火星 此题找出规律即可 #include<bits/stdc.h> using namespace std; int n; int main() {cin >> n;cout << max(0, n - 2);return 0; } 37.P1551 亲戚 并查集模板题目 两个人如果使亲戚就合并建立联系&#xff0c;最后进行查找即可 #incl…

动态规划:解决复杂问题的利器(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

使用tomcat搭建简易文件服务器

步骤 1、在本机另外部署一个tomcat作为文件服务器 可以像我这样将tomcat文件复制一个做为服务器 2、在webapps下新建文件夹uploadfiles&#xff0c;这个文件夹就是用来存储上传的文件的 &#xff08;记住一定要是在作为服务器的tomcat的webapps下&#xff09; 3、修改conf/…

nvm for windows使用与node/npm/yarn的配置

1 下载 nvm for windows download – github 下拉到Assets, 下载.exe文件 2 安装 安装到如下文件夹中 目录可以自己选, 可以换别的名字, 自己记住即可 新手建议全部看完再进行个人配置, 或者使用与博主一致的路径 D:\DevelopEnvironment\nvm3 配置nvm使用的镜像 node_mir…

基于opencv+ImageAI+tensorflow的智能动漫人物识别系统——深度学习算法应用(含python、JS、模型源码)+数据集(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境爬虫模型训练实际应用 模块实现1. 数据准备1&#xff09;爬虫下载原始图片2&#xff09;手动筛选图片 2. 数据处理1&#xff09;切割得到人物脸部2&#xff09;重新命名处理后的图片3&#xff09;添加到数据集 3. 模型训练及…