我正在编写一个用于进程间通信的程序,但我遇到了一个问题,即使管道中有足够的空间,写入操作也会阻塞进程.

我正在使用一个管道缓冲区大小为8192的远程主机,这要归功于以下几点:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {

    int fd[2];
    pipe(fd);

    printf("Pipe size: %d\n", fcntl(fd[1], F_GETPIPE_SZ));

    close(fd[1]);
    close(fd[0]);

    return 0;
}

在下面的示例中,我创建了16个进程,每个进程都有自己的管道. 然后,每个进程将512B写入其子进程的管道. 子元素们读到了这些信息. 根被标记为0,子进程被连续编号为2k+1, 2k+2,其中k是进程号. 最后,每个进程向所有管道发送一条消息.

因此,16*512B=8192将被写入根的管道,并被写入每隔一个管道(16+1)*512B=8192+512,但是将读取一条额外的消息,因此整个消息应该可以放入管道中.

MRE(此示例没有做任何有用的事情;它只说明了我的问题):

#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

#define NO_OF_PROCESSES 16
#define NO_OF_MESSAGES 1
#define ROOT 0

#define ERROR_CHECK(result) \
do { \
    if ((result) == -1) { \
        fprintf(stderr, "Error at line %d\n", __LINE__); \
        exit(1); \
    } \
} while (0)

#define NOT_PARTIAL(result) \
do { \
    if ((result) != 512) { \
        fprintf(stderr, "Error at line %d\n", __LINE__); \
        exit(1); \
    } \
} while (0)

void close_pipes(int fd[NO_OF_PROCESSES][2]) {
    for (int i = 0; i < NO_OF_PROCESSES; i++) {
        ERROR_CHECK(close(fd[i][0]));
        ERROR_CHECK(close(fd[i][1]));
    }
}

void child_code(int fd[NO_OF_PROCESSES][2], int child_id) {

    void* message = malloc(512);
    if (message == NULL)
        exit(EXIT_FAILURE);

    memset(message, 0, 512);

    int l = 2 * child_id + 1;
    int r = 2 * child_id + 2;

    // Every process sends two messages to each of its children.
    if (child_id == ROOT || l < NO_OF_PROCESSES) { // Root or any other parent.

        if (child_id != ROOT)
            for (int i = 0; i < NO_OF_MESSAGES; i++)
                NOT_PARTIAL(read(fd[child_id][0], message, 512));

        if (l < NO_OF_PROCESSES)
            for (int i = 0; i < NO_OF_MESSAGES; i++)
                NOT_PARTIAL(write(fd[l][1], message, 512));

        if (r < NO_OF_PROCESSES)
            for (int i = 0; i < NO_OF_MESSAGES; i++)
                NOT_PARTIAL(write(fd[r][1], message, 512));
    }
    else { // Leaf.
        for (int i = 0; i < NO_OF_MESSAGES; i++)
            NOT_PARTIAL(read(fd[child_id][0], message, 512));
    }

    printf("Ok, process %d\n", child_id);

    // Process sends one message to every other process.
    for (int i = 0; i < NO_OF_PROCESSES; i++) {
        int pipe_size = 0;
        ioctl(fd[i][1], FIONREAD, &pipe_size);
        printf("Check_1, process %d, there are %d bytes in the pipe, iteration %d\n", child_id, pipe_size, i);
        NOT_PARTIAL(write(fd[i][1], message, 512));
        printf("Check_2, process %d\n", child_id);
        fflush(stdout);
    }

    free(message);

    printf("Finished, process %d\n", child_id);
}

int main() {

    // Each child has its own pipe.
    int fd[NO_OF_PROCESSES][2];
    for (int i = 0; i < NO_OF_PROCESSES; i++) {
        ERROR_CHECK(pipe(fd[i]));
    }

    // Creating children processes.
    for (int i = 0; i < NO_OF_PROCESSES; i++) {

        int fork_result = fork();
        ERROR_CHECK(fork_result);

        if (fork_result== 0) { // Child process.
            child_code(fd, i);
            close_pipes(fd);
            return 0;
        }
    }

    close_pipes(fd);

    // Waiting for all children to finish.
    for (int i = 0; i < NO_OF_PROCESSES; i++) {
        ERROR_CHECK(wait(NULL));
    }

    return 0;
}

目前,结果是程序不会因为某些进程挂起而终止.

输出的最后几行:

Ok, process 12
Check_1, process 2, there are 7168 bytes in the pipe, iteration 15
Check_2, process 2
Check_1, process 12, there are 7680 bytes in the pipe, iteration 0
Finished, process 2
Check_2, process 12
Check_1, process 12, there are 7680 bytes in the pipe, iteration 1

正如您所看到的,Check_2, process 12丢失了,进程在写入时挂起,即使在完整的输出中Ok出现了16次,这从理论上意味着应该读取"树中"的所有消息.

该程序适用于15个或更少的进程,因为这样最多就有8192B进入管道.类似地,代码在管道容量较大的系统上运行.

我在哪里犯了错?为什么这一过程会暂停? 如果我的代码适合您,那么您可能在管道中有不同的缓冲区大小.

最近(相当笨拙地)我问了一个类似的问题.我正在添加一个新的帖子,而不是编辑旧的帖子,因为整个内容都会改变,现有的答案将不再有意义. 我希望这个帖子更好.

非常感谢.

推荐答案

我哪里做错了?

由于程序的输出似乎显示管道缓冲区中有足够的空间来容纳最后一个进程试图写入的数据,但写入仍然挂起,因此只有几个合理的解释:

  1. 您的系统有一些您没有考虑到的额外限制.例如,对任何给定时间在all个管道中缓冲的聚合数据的限制.

  2. 您的系统有一个您的程序设法触发的错误.

您没有提供任何系统细节,因此我们不能提供更多细节.然而,我注意到,即使在我的系统上调整了管道缓冲区大小(65536字节)之后,我也不能重现您的程序的挂起.因此,我确实认为您观察到的行为是特定于系统的.

尽管如此,我可以从更高的层面回答这个问题:您的错误在于将数据写入管道,而您并不期望数据会被读取.管道是数据transfer机制,而不是数据storage机制.作为程序员,您有责任确保在您控制的范围内,您写入管道的数据也将从管道中使用.

Addendum

作为次要的、更务实的问题,在需要的时候让管道末端保持打开状态可能会导致各种问题.从这个Angular 来看,父进程在完成所有子进程的派生后立即关闭其所有管道末端的副本是很好的.但在启动时,子元素们应该关闭除他们自己的管道之外的所有管道的读取端,并关闭他们自己的管道的写入端.我预计,如果他们这样做,那么程序将不会挂起,而是一些写入将失败,并返回EPIPE.As they should.

C++相关问答推荐

librsvg rsvg_handle_get_dimensions获取像素大小与浏览器中的渲染大小没有不同

函数指针始终为零,但在解除引用和调用时有效

错误:在.h程序中重新定义 struct

如何启用ss(另一个调查套接字的实用程序)来查看Linux主机上加入的多播组IP地址?

intellisense不工作,甚至已经下载了c/c++扩展

需要大整数和浮点数.使用long long int和long double

有没有更简单的方法从用户那里获取数据类型来计算结果

C编译器是否遵循restrict的正式定义?

C语言中的strstr问题

C由四个8位整数组成无符号32位整数

正确的TCP/IP数据包 struct

二进制计算器与gmp

防止C++中递归函数使用堆栈内存

C11/C17标准允许编译器清除复合文字内存吗?

如何在VSCode中创建和使用我自己的C库?

将数字的每一位数平方,并使用C将它们连接为一个数字(程序不能正确处理0)

具有正确标头的C struct 定义问题

如何解释数组中的*(ptr)和*(ptr+2)?

c如何传递对 struct 数组的引用,而不是设置 struct 的副本

malloc 属性不带参数