我正在编写一个用于进程间通信的程序,但是我遇到了一个问题,即使管道中有足够的空间,写入操作也会阻塞进程。
我正在使用管道缓冲区大小为 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 会进入管道。同样,该代码适用于管道容量较大的系统。
我哪里出错了?为什么进程挂起? 如果我的代码适合您,也许您的管道中有不同的缓冲区大小。
最近(相当笨拙)我问了类似的问题。我添加一篇新帖子而不是编辑旧帖子,因为整个内容都会改变,并且现有答案将不再有意义。 希望这篇文章越来越好。
非常感谢。
我哪里出错了?
由于程序的输出似乎表明管道缓冲区中有足够的空间来容纳最后一个进程尝试写入的数据,但写入仍然挂起,因此只有一些合理的解释:
您的系统还有一些您没有考虑到的额外限制。例如,在任何给定时间对“所有”管道中缓冲的聚合数据进行限制。
尽管如此,我可以从高层次上回答这个问题:您的错误在于当您不期望数据会被读取时将数据写入管道。管道是一种数据
传输机制,而不是数据存储机制。作为程序员,您有责任确保在您的控制范围内,您写入管道的数据也将从管道中被消耗。