引入
之前我们介绍了多进程以及创建进程的函数fork
,下面我们将继续深入,讨论一下多进程间的通信问题;
pipe 管道
谈论多进程通信,就离不开pipe
(管道),这是一个系统调用,用于在 UNIX 和类 UNIX 系统(如 Linux)上创建一个管道(pipe),实现进程间通信。它创建了一个双向的通信通道,允许一个进程向另一个进程发送数据。管道是单向的,即数据只能沿一个方向流动:从读端读取数据,从写端写入数据。
所以我们需要分别关闭管道两端的读取端和写入端,使得进程间信息可以单向传输;
函数定义
int pipe(int pipefd[2]);
- 参数:
- pipefd[2]:一个包含两个整数的数组,用来保存两个文件描述符(file descriptor):
- pipefd[0] 是管道的读端(用于读取数据);
- pipefd[1] 是管道的写端(用于写入数据);
- pipefd[2]:一个包含两个整数的数组,用来保存两个文件描述符(file descriptor):
- 返回值:
- 成功时返回
0
:表示管道创建成功; - 失败时返回
-1
:并设置errno
表示
- 成功时返回
工作原理
pipe() 系统调用创建一个可以在进程之间传递数据的管道,它会生成两个文件描述符:
- pipefd[0]:用于从管道的读端读取数据。
- pipefd[1]:用于向管道的写端写入数据。
在典型的使用场景中:
- 父进程可以创建一个管道,然后使用 fork() 创建子进程。
- 子进程可以关闭管道的写端,通过读端读取父进程发送的数据。
- 父进程可以关闭管道的读端,通过写端向子进程发送数据。
管道是一个单向的通信通道,因此数据只能从写端发送到读端。如果想要双向通信,需要创建两个管道,一个用于从父进程到子进程,另一个用于从子进程到父进程; 下面是示例代码:
#include <iostream>
#include <unistd.h>
#include <cassert>
int main() {
int pipefd[2]; // 保存管道的读端和写端
char buffer[100];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe failed");
return 1;
}
// 创建子进程
pid_t pid = fork();
assert(pid >= 0);
if (pid == 0) { // 子进程
close(pipefd[1]); // 关闭写端
// 从管道读端读取数据
read(pipefd[0], buffer, sizeof(buffer));
std::cout << "子进程读取到的消息: " << buffer << std::endl;
close(pipefd[0]);
} else { // 父进程
close(pipefd[0]); // 关闭读端
const char* message = "Hello from parent!";
// 向管道写端写入数据
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
}
return 0;
}
解释
- 父进程创建了一个管道,通过 pipefd[0] 和 pipefd[1] 分别代表管道的读端和写端。
- 父进程使用 fork() 创建子进程,子进程会继承父进程的文件描述符。
- 在子进程中,关闭写端,然后通过管道的读端 pipefd[0] 读取父进程写入的数据。
- 在父进程中,关闭读端,然后通过管道的写端 pipefd[1] 写入一条消息。
常见问题
-
单向通信: 管道的一个限制是它只能进行单向通信。如果需要双向通信,可以使用两个管道,或者使用其他更复杂的 IPC(进程间通信)机制,如套接字、共享内存等。
-
管道阻塞:
- 如果管道的写端没有被关闭,读操作可能会被阻塞,直到有数据可供读取。
- 同样,如果管道的读端没有被关闭,写操作可能会阻塞,直到缓冲区有空间来写数据。
- 关闭文件描述符: 在父子进程中使用管道时,确保正确关闭不需要的文件描述符。例如,父进程应该关闭读端,而子进程应该关闭写端。否则,管道通信可能会出现错误。
管道的应用场景
-
进程间通信: 管道最常见的用途是实现父子进程之间的简单数据通信,尤其是在 fork() 之后。
-
子进程的输出重定向: 管道可以用于将子进程的标准输出重定向到父进程,以便父进程读取子进程的输出。
-
流水线处理: 在命令行中,使用管道 | 实现将一个程序的输出传递给另一个程序,正是基于 pipe() 的概念。例如:
ls | grep *.cpp
# 将ls的输出通过管道传递给 grep 进行过滤
注意,上述谈论的管道主要是匿名管道
,与之对应的还有命名管道
,这里不做深究,下一篇文章可以详细讲解一下二者之间的区别;
代码框架
有了上述的pipe
管道函数的介绍,我们就可以编写一个用于测试父子进程间通信的程序,接下来我们将分析这个程序的主要功能及其实现方法,搭建出一个简易的框架:
- 首先我们需要一串父子进程,用于传输和接收信息,可以使用
fork
函数进行创建,同时还需要储存相应进程的信息,方便我们进行操作; - 此外,我们还需要一个传输函数和接收函数,用于父进程发送信息和子进程接收信息;
- 同时,我们还需要一个待处理任务组,用于子进程处理相关的任务,这里为了简化操作,我们将采用简单的示例代码,只是为了检测相应的功能;
- 关于我们传递的信息内容,我们需要传递执行任务的子进程编号以及执行的任务编号,以便于子进程可以实现
负载均衡
;
代码实现
完成了上述的准备工作,我们就可以着手代码实现了,关于代码中的细节问题,注释中都有相应的解释,我后续也会再去完善相应的注释解释工作,代码具体见于我的GitHub仓库,实现代码在pipe分支;
标签:pipe,创建,pipefd,间通信,管道,Linux,进程,读端 From: https://blog.51cto.com/u_16271511/12023713