Linux多进程和多线程(二)-进程间通信-管道用法

  • 进程间通信
    • 关于多进程的通信
    • 管道
    • 无名管道(匿名管道)
    • 创建无名管道
    • 示例:创建子进程,父进程通过管道向子进程发送消息
    • 无名管道(匿名管道) 的特点
  • 有名管道(命名管道)
    • 创建有名管道需要调⽤ mkfifo() 函数
    • 示例:创建两个没有关联关系的进程,通过有名管道通信
  • 注意:
    • 缺点
    • 优点
  • 关于判断管道是否存在,可以使用errno
    • errno

进程间通信

关于多进程的通信

  • linux 下的进程通信⼿段基本上是从 Unix 平台上的进程通信⼿段继承⽽来的。
  • 每个进程都有⾃⼰独⽴的地址空间, 当两个不同进程需要进⾏交互时, 就需要使⽤进程间通讯
  • 进程间通讯分为单个计算机的进程间通讯与局域⽹的计算机的进程间通讯
  • 进程间通讯⽅式有 管道, 信号, 消息队列, 共享内存,⽹络

管道

  • 管道分为 ⽆名管道(匿名管道) 与 有名管道
    • ⽆名管道⽤于⽗⼦进程之间通讯
    • 有名管道⽤于任意进程之间通讯

管道的本质是在内存建⽴⼀段缓冲区,由操作系统内核来负责创建与管理, 具体通讯模型如下:

在这里插入图片描述

无名管道(匿名管道)

- 无名管道属于单向通讯
- ⽆名管道只能⽤于 ⽗⼦进程通讯
- ⽆名管道发送端叫做 写端, 接收端叫做 读端
- ⽆名管道读端与写端抽象成两个⽂件进⾏操作,在⽆名管道创建成功之后,则会返回读端与写端的⽂件描述符

创建无名管道

  • 创建⽆名管道需要系统调用 pipe()
  • pipe() 函数原型如下:

在这里插入图片描述

#include <unistd.h>int pipe(int pipefd[2]);
  • pipefd[2] 是一个数组,数组的两个元素分别代表读端和写端的⽂件描述符
  • pipefd[0] 代表读端,pipefd[1] 代表写端
  • 函数成功返回 0,否则返回 -1 并设置 errno 变量

示例:创建子进程,父进程通过管道向子进程发送消息

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int main() {//创建子进程,父进程通过管道向子进程发送消息pid_t cpid;//子进程IDint ret;//返回值int pipefd[2];//管道文件描述符ret = pipe(pipefd);//创建管道,内核会将文件描述符号放入pipefd数组中if (ret < 0) {//创建失败perror("pipe");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}cpid = fork();//创建子进程if (cpid < 0) {//创建失败perror("fork");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}else if (cpid == 0) {//子进程ssize_t rbytes;//此变量在父进程不存在,在子进程中使用 //ssize_t是read函数的返回类型,表示读取的字节数close(pipefd[1]);//关闭写端,子进程只读//操作管道char buf[1024];printf("子进程开始等待父进程发送消息...\n");rbytes = read(pipefd[0], buf, sizeof(buf));//从管道中读取数据,当管道没有数据,没有相应的进程往管道写,这里会阻塞if (rbytes < 0) {//读取失败perror("read");//打印错误信息close(pipefd[0]);//关闭读端exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//读到了数据printf("子进程收到消息: %s\n", buf);//打印接收到的消息//操作结束close(pipefd[0]);//最后关闭子进程的读端,避免阻塞}else if (cpid > 0) {//父进程ssize_t wbytes;//此变量在父进程中使用,在子进程不存在close(pipefd[0]);//关闭读端,父进程只写//操作管道char buf[1024];strcpy(buf, "Hello, child!");//发送的消息wbytes = write(pipefd[1], buf, strlen(buf));if (wbytes < 0) {//发送失败perror("write");//打印错误信息wait(NULL);//此时写错误要,等待子进程结束close(pipefd[1]);//关闭写端exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//发送成功printf("父进程发送消息: %s\n", buf);//打印发送的消息//操作结束close(pipefd[1]);//最后关闭父进程的写端,wait(NULL);//正常等待子进程结束}return 0;
}

无名管道(匿名管道) 的特点

当管道为空,读管道会阻塞读进程

当管道的写端被关闭了,从管道中读取剩余的数据后 会返回0,表示管道已经被关闭

在写入管道时,确保不会超过管道的容量, PIPE_BUF 4096;//管道缓冲区大小

  • 当写入的数据达到PIPE_BUF时,write函数会阻塞,直到管道中有足够的空间以原子的方式完成方式

  • 当写入的数据大于PIPE_BUF时,write函数会尽可能多的传输数据,充满这个管道

管道的大小是有限的,不能让父/子进程同时对管道进行读/写操作,否则会导致数据混乱

当一个进程试图向一个管道写入数据,但是没有任何进程拥有该管道的打开着的读取描述符,内核向写入进程发送一个SIGPIPE信号

有名管道(命名管道)

有名管道是在 ⽂件系统中可⻅的⽂件, 但是不占⽤磁盘空间, 仍然在内存中, 可以通过 mkfifo 命令创建有名管道

有名管道与⽆名管道⼀样,在应⽤层是基于⽂件接口进⾏操作

有名管道⽤于 任意进程之间的通讯, 当管道为空时, 读进程会阻塞.

在文件系统中:
文件名以 "p"开头,说明是管道文件,他占用的是内存空间, 并不占用磁盘空间
在这里插入图片描述

创建有名管道需要调⽤ mkfifo() 函数

函数头文件:

#include <sys/types.h>
#include <sys/stat.h>

函数原型:

int mkfifo(const char *pathname, mode_t mode);

参数:

  • pathname: 管道文件路径名
  • mode: 管道文件的权限, 如 0666

函数返回值:
成功返回0, 失败返回-1, 并设置errno变量

示例:创建两个没有关联关系的进程,通过有名管道通信

  • 读的进程代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/** 无名管道(匿名管道) 和 有名管道(命名管道)* *//** 无名管道(匿名管道)*/
//创建子进程,父进程通过管道向子进程发送消息
int main1() {pid_t cpid;//子进程IDint ret;//返回值int pipefd[2];//管道文件描述符//PIPE_BUF ;//管道缓冲区大小 4096ret = pipe(pipefd);//创建管道,内核会将文件描述符号放入pipefd数组中if (ret < 0) {//创建失败perror("pipe");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}cpid = fork();//创建子进程if (cpid < 0) {//创建失败perror("fork");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}else if (cpid == 0) {//子进程ssize_t rbytes;//此变量在父进程不存在,在子进程中使用 //ssize_t是read函数的返回类型,表示读取的字节数close(pipefd[1]);//关闭写端,子进程只读//操作管道char buf[1024];printf("子进程开始等待父进程发送消息...\n");rbytes = read(pipefd[0], buf, sizeof(buf));//从管道中读取数据,当管道没有数据,没有相应的进程往管道写,这里会阻塞if (rbytes < 0) {//读取失败perror("read");//打印错误信息close(pipefd[0]);//关闭读端exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//读到了数据printf("子进程收到消息: %s\n", buf);//打印接收到的消息//操作结束close(pipefd[0]);//最后关闭子进程的读端,避免阻塞}else if (cpid > 0) {//父进程ssize_t wbytes;//此变量在父进程中使用,在子进程不存在close(pipefd[0]);//关闭读端,父进程只写//操作管道char buf[1024];strcpy(buf, "Hello, child!");//发送的消息wbytes = write(pipefd[1], buf, strlen(buf));if (wbytes < 0) {//发送失败perror("write");//打印错误信息wait(NULL);//此时写错误要,等待子进程结束close(pipefd[1]);//关闭写端exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//发送成功printf("父进程发送消息: %s\n", buf);//打印发送的消息//操作结束close(pipefd[1]);//最后关闭父进程的写端,wait(NULL);//正常等待子进程结束}return 0;
}/** 有名管道(命名管道)*/
//创建两个没有关联关系的进程,通过有名管道通信
//下方是读的进程代码,写的进程代码在另外一个文件Process2中
#define PATHNAME "/home/gopher/ClionWork/fifo_test"
int main(){int  ret;//创建有名管道返回值int fd;//打开文件返回值ssize_t rbytes;//读取文件返回值char buf[1024]={0};//读取文件缓冲区ret= mkfifo(PATHNAME, 0666);//创建有名管道//  如果管道存在了,下面代码判断条件会报错
//    if (ret < 0) {//创建失败
//        perror("mkfifo");//打印错误信息
//        exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1
//    }//使用新的判断条件保证管道存在,不会报错//关于  errno  文章末尾有更多介绍 他是一个全局变量,用来存放系统调用的错误信息,它用于指示最近一次系统调用或库函数调用中发生的错误类型。//errno 等于 ( EEXIST = 17 表示文件存在 EEXIST 是 POSIX 标准中的一个宏) 表示管道已经存在if (ret < 0) { // 创建失败if (errno == EEXIST) {// 管道已经存在printf("管道已存在,继续执行程序。\n");} else {// 打印错误信息perror("mkfifo");// 退出程序exit(EXIT_FAILURE);}} else {printf("管道创建成功。\n");}fd = open(PATHNAME, O_RDWR);//打开有名管道if (fd < 0) {//打开失败perror("open");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//开始读printf("进程等待读消息...\n");rbytes = read(fd, buf, sizeof(buf));if (rbytes < 0) {//读取失败perror("read");//打印错误信息close(fd);//关闭有名管道exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}printf("进程收到消息: %s\n", buf);//打印接收到的消息//读结束close(fd);//关闭有名管道return 0;
}
  • 写的进程代码
/** 有名管道(命名管道)*/
//创建两个没有关联关系的进程,通过有名管道通信
//这是写的进程
#define PATHNAME "/home/gopher/ClionWork/fifo_test"
int main(){
//    int  ret;//创建有名管道返回值int fd;//打开文件返回值ssize_t wbytes;//读取文件返回值char buf[]={"hello world"};//读取文件缓冲区//读的进程已经创建了
//    ret= mkfifo(PATHNAME, 0666);//创建有名管道
//    if (ret < 0) {//创建失败
//        perror("mkfifo");//打印错误信息
//        exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1
//    }fd = open(PATHNAME, O_WRONLY);//打开有名管道if (fd < 0) {//打开失败perror("open");//打印错误信息exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}//写的进程printf("进程写入消息...\n");wbytes = write(fd, buf, strlen(buf));//写入消息到有名管道if (wbytes < 0) {//读取失败perror("read");//打印错误信息close(fd);//关闭有名管道exit(EXIT_FAILURE);//退出程序 EXIT_FAILURE 1}close(fd);//关闭有名管道return 0;
}

注意:

如果有名管道的⼀端以只读⽅式打开,它会阻塞到另⼀端以写的⽅式 (只写,读写)

如果有名管道的⼀端以只写⽅式打开,它会阻塞到另⼀端以读的⽅式 (只读,读写)

如果有名管道的⼀端以读写⽅式打开,它不会阻塞,因为读写方式既支持读取又支持写入,满足了管道通信的要求。

有名管道在不同情况下的阻塞特性。当有名管道的一端以只读或只写模式打开时,会等待另一端的对应模式(写或读)来完成通信,从而进行阻塞。而当管道的一端以读写模式打开时,由于该模式同时支持读取和写入,所以不会出现阻塞现象。

确保在合适的场景下选择合适的文件打开模式,避免不必要的阻塞,从而提高程序运行的效率和稳定性。

缺点

  • 打开时需要读写一起进行,否则会阻塞,管道大小是PIPE_BUF 4096字节
  • 半双工的工作模式,如果和多个进程通信,需要创建多个管道

半双工(Half-Duplex)通信是一种数据传输模式,允许设备在同一个信道上进行双向通信,但不能同时进行。

这意味着同一时间内,数据只能在一个方向上传输,而另一个方向则需要等待。

举个例子,对讲机就是典型的半双工设备。当一个人在发言时,另一方只能接收,无法同时发言。要进行双向交流,双方需要轮流按下发话键来切换发送和接收状态。

半双工通信的优点包括:

节省带宽:因为信道资源是共享的,减少了频带的使用。

简单实现:相对于全双工,半双工的实现原理和机制更为简单,有助于降低设备成本。

缺点则主要在于效率较低,因为在同一个时间内只能单方向传输数据,可能导致通信延迟。

优点

  • 可以实现任意进程间的通信,操作起来和文件操作一样

关于判断管道是否存在,可以使用errno

errno

使用他要引入errno.h

errno 是一个全局变量,在几乎所有的 C 语言和 POSIX 标准的编程环境中都存在。

它用于指示最近一次系统调用或库函数调用中发生的错误类型。

即,通过检查 errno 的值,程序员可以确定错误发生的原因并进行相应的处理。

每次系统调用或库函数调用失败时,会设置 errno 的值。
不同的错误类型由不同的错误码标示,
例如,EEXIST 表示试图创建一个已经存在的文件或目录,ENOMEM 表示内存不足等等。

在 POSIX 的环境中,以下是一些常用的错误码及其说明:
所有错误码都定义在 errno-base.h 头文件中。

EEXIST - 文件已存在。

ENOENT - 文件或目录不存在。

ENOMEM - 内存不足。

EACCES - 权限被拒绝。

EINVAL - 无效的参数。

EBUSY - 设备或资源正忙。

EAGAIN - 资源暂时不可用。

EPIPE - 管道已关闭。

EINTR - 系统调用被中断。

EIO - 输入/输出错误。

EISDIR - 试图打开一个目录。

EFBIG - 文件太大。

EFBIG - 文件太大。

EOVERFLOW - 值过大。

会设置 errno 的值。
不同的错误类型由不同的错误码标示,
例如,EEXIST 表示试图创建一个已经存在的文件或目录,ENOMEM 表示内存不足等等。

在 POSIX 的环境中,以下是一些常用的错误码及其说明:
所有错误码都定义在 errno-base.h 头文件中。

EEXIST - 文件已存在。

ENOENT - 文件或目录不存在。

ENOMEM - 内存不足。

EACCES - 权限被拒绝。

EINVAL - 无效的参数。

EBUSY - 设备或资源正忙。

EAGAIN - 资源暂时不可用。

EPIPE - 管道已关闭。

EINTR - 系统调用被中断。

EIO - 输入/输出错误。

EISDIR - 试图打开一个目录。

EFBIG - 文件太大。

EFBIG - 文件太大。

EOVERFLOW - 值过大。

errno 提供了一种标准化的方式来报告和处理错误,使得程序可靠和健壮。

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

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

相关文章

Linux多进程和多线程(一)-进程的概念和创建

进程 进程的概念进程的特点如下进程和程序的区别LINUX进程管理 getpid()getppid() 进程的地址空间虚拟地址和物理地址进程状态管理进程相关命令 ps toppstreekill 进程的创建 并发和并行fork() 父子进程执行不同的任务创建多个进程 进程的退出 exit()和_exit() exit()函数让当…

七日世界Once Human跳ping、延迟高、丢包怎么办?

七日世界是一款开放世界为轴点的生存射击游戏&#xff0c;玩家将进入一个荒诞、荒芜的末日世界&#xff0c;在这里与好友一起对抗可怖的怪物和神秘物质星尘的入侵&#xff0c;给这个星球留下最后的希望&#xff0c;共筑一片安全的领地。不过有部分玩家在游玩七日世界的时候&…

昇思MindSpore学习笔记7--函数式自动微分

摘要&#xff1a; 介绍了昇思MindSpore神经网络训练反向传播算法中函数式自动微分的使用方法和步骤。包括构造计算函数和神经网络、grad获得微分函数&#xff0c;以及如何处理停止渐变、获取辅助数据等内容。 一、概念要点 神经网络训练主要使用反向传播算法&#xff1a; 准备…

从AICore到TensorCore:华为910B与NVIDIA A100全面分析

华为NPU 910B与NVIDIA GPU A100性能对比&#xff0c;从AICore到TensorCore&#xff0c;展现各自计算核心优势。 AI 2.0浪潮汹涌而来&#xff0c;若仍将其与区块链等量齐观&#xff0c;视作炒作泡沫&#xff0c;则将错失新时代的巨大机遇。现在&#xff0c;就是把握AI时代的关键…

RAG 基本流程及处理技巧 with LangChain

LLM 主要存在两个问题&#xff1a;幻想和缺乏领域知识。领域知识缺乏的原因是因为训练 LLM 本身的知识更新慢&#xff0c;对特定领域的知识也没有太细致的输入。 RAG 主要是解决 LLM 缺乏领域知识的问题。底层的逻辑是&#xff1a;把 LLM 作为逻辑推理引擎&#xff0c;而不是信…

机器学习--概念理解

知识点 一、机器学习概述 人工智能 机器学习 深度学习 学习的范围&#xff1a;模式识别、数据挖掘、统计学习、计算机视觉、语音识别、自然语言处理 可以解决的问题&#xff1a;给定数据的预测问题 二、机器学习的类型 监督学习 分类 回归 无监督学习 聚类 降维 强化…

恢复的实现技术-日志和数据转储

一、引言 在系统正常运行的情况下&#xff0c;事务处理的恢复机制应采取某些技术措施为恢复做好相应的准备&#xff0c;保证在系统发生故障后&#xff0c;能将数据库从一个不一致的错误状态恢复到一个一致性状态 恢复技术主要包括 生成一个数据库日志&#xff0c;来记录系统中…

Unity制作一个简单抽卡系统(简单好抄)

业务流程&#xff1a;点击抽卡——>播放动画——>显示抽卡面板——>将随机结果添加到面板中——>关闭面板 1.准备素材并导入Unity中&#xff08;包含2个抽卡动画&#xff0c;抽卡结果的图片&#xff0c;一个背景图片&#xff0c;一个你的展示图片&#xff09; 2.给…

创建一个vue3+vite+ts项目

目录 创建项目 ​编辑 下载jsx 插件 在根目录在新建.env vue.config.js tsconfig.json tsconfig.node.json 下载ui组件库和路由&#xff08;组件库根据自己的项目需要选择&#xff09; 在根目录下新建views/index.tsx 在根目录下新建router/index.ts 修改App.vue 创建…

机器学习原理之 -- 朴素贝叶斯分类器:由来及原理详解

朴素贝叶斯&#xff08;Naive Bayes&#xff09;分类器是一类基于贝叶斯定理&#xff08;Bayes Theorem&#xff09;的简单而有效的概率分类算法。由于其假设特征之间的条件独立性&#xff0c;因此被称为“朴素”贝叶斯分类器。尽管这种独立性假设在现实中很少完全成立&#xf…

mac系统docker默认不支持host主机网络模式

环境描述&#xff1a;在mac系统上安装docker及docker-compose服务&#xff0c;并且打算搭建一个redis集群 问题描述&#xff1a;mac默认不支持host网络模式&#xff0c;导致集群无法通过外部主机访问 具体验证步骤&#xff1a; docker-compose.yml如下&#xff1a; version…

reactor网络模型的原理与实现

一、rector网络模型 对高并发编程&#xff0c;网络连接上的消息处理&#xff0c;可以分为两个阶段&#xff1a;等待消息准备好、消息处理。当使用默认的阻塞套接字时&#xff0c;往往是把这两个阶段合而为一&#xff0c;这样操作套接字的代码所在的线程就得睡眠来等待消息准备好…

Mysql常用SQL:日期转换成周_DAYOFWEEK(date)

有时候需要将查询出来的日期转换成周几&#xff0c;Mysql本身语法就是支持这种转换的&#xff0c;就是DAYOFWEEK()函数 语法格式&#xff1a;DAYOFWEEK(date) &#xff08;date&#xff1a;可以是指定的具体日期&#xff08; 如2024-06-29 &#xff09;&#xff0c;也可以是日期…

为什么word生成的PDF内容显示不全?

在现代办公环境中&#xff0c;将文档从一个格式转换为另一个格式是一个常见的任务。然而&#xff0c;有时候我们可能会遇到意想不到的问题&#xff0c;比如使用Word转换成PDF时&#xff0c;生成的PDF文件只显示了整个界面的四分之一内容。这种问题不仅令人困扰&#xff0c;也可…

斜率优化DP——AcWing 303. 运输小猫

斜率优化DP 定义 斜率优化DP&#xff08;Slope Optimization Dynamic Programming&#xff09;是一种高级动态规划技巧&#xff0c;用于优化具有特定形式的状态转移方程。它主要应用于那些状态转移涉及求极值&#xff08;如最小值或最大值&#xff09;的问题中&#xff0c;通…

CesiumJS【Basic】- #028 天空盒

文章目录 天空盒1 目标2 代码2.1 main.ts3 资源天空盒 1 目标 配置显示天空盒 2 代码 2.1 main.ts import * as Cesium from cesium;// 创建 Cesium Viewer 并配置地形数据和天空盒 const viewer = new Cesium.Viewer(

理解抽象工厂设计模式

目录 抽象工厂模式抽象工厂模式结构抽象工厂模式适合应用场景抽象工厂模式优缺点练手题目题目描述输入描述输出描述提示信息题解 抽象工厂模式 抽象工厂模式是一种创建型设计模式&#xff0c; 它能创建一系列相关的对象&#xff0c; 而无需指定其具体类。 抽象工厂模式结构 抽…

自定义一个MyBaits脱敏插件

自定义一个MyBaits脱敏插件 用于对查询结果中的敏感数据进行脱敏处理。这个插件将拦截ResultSetHandler对象的处理结果&#xff0c;对某些敏感字段进行脱敏。 插件实现步骤 创建脱敏插件类。注册插件。 1. 创建脱敏插件类 首先&#xff0c;我们创建一个自定义插件类 DataM…

深入理解策略梯度算法

策略梯度&#xff08;Policy Gradient&#xff09;算法是强化学习中的一种重要方法&#xff0c;通过优化策略以获得最大回报。本文将详细介绍策略梯度算法的基本原理&#xff0c;推导其数学公式&#xff0c;并提供具体的例子来指导其实现。 策略梯度算法的基本概念 在强化学习…

【Python3的内置函数和使用方法】

目录 Python 特点 Python 中文编码 Python 变量类型 Python列表 Python 元组 元组是另一个数据类型&#xff0c;类似于 List&#xff08;列表&#xff09; Python 字典 Python数据类型转换 Python 运算符 Python算术运算符 Python比较运算符 Python赋值运算符 Pyt…