如何解决尽管管道的写入端已关闭,但为什么 read() 会阻塞并在父进程中永远等待?
我正在编写一个程序,其中包含两个通过管道进行通信的进程。子进程从父进程读取一些参数,用它们执行shell脚本并将结果逐行返回给父进程。
我的代码运行良好,直到我在父进程结束时编写了 while(read())
部分。子进程将执行 shell 脚本,从 popen()
读取其回显并将它们打印到标准输出。
现在我也尝试将结果写入管道并在父端的 while()
循环中读取它们,但它阻塞了,子进程也不会将结果打印到标准输出。显然,从父发送的管道中读取数据后,它甚至不会达到这一点。
如果我在父进程注释掉while()
,子进程会打印结果并返回,程序会顺利结束。
为什么即使我关闭了父进程和子进程中管道的写入端,while(read())
也会阻塞?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
int read_from_file(char **directory,int *octal) {
FILE *file = fopen("input","r");
if (file == NULL) {
perror("error opening file");
exit(1);
}
fscanf(file,"%s %d",*directory,octal);
}
int main(int argc,char *argv[]) {
char *directory = malloc(256);
int *octal = malloc(sizeof *octal);
pid_t pid;
int pfd[2];
char res[256];
if (pipe(pfd) < 0) {
perror("Error opening pipe");
return 1;
}
if ((pid = fork()) < 0)
perror("Error forking");
if (pid == 0) {
printf("client here\n");
if (read(pfd[0],directory,256) < 0)
perror("error reading from pipe");
if (read(pfd[0],octal,sizeof(int)) < 0)
perror("error reading from pipe");
// This won't get printed:
printf("client just read from pipe\n");
// close(pfd[0]);
char command[256] = "./asd.sh ";
strcat(command,directory);
char octal_c[5];
sprintf(octal_c," %d",*octal);
strcat(command,octal_c);
FILE *f = popen(command,"r");
while (fgets(res,256,f) != NULL) {
printf("%s",res);
if (write(pfd[1],res,256) < 0)
perror("Error writing res to pipe");
}
fclose(f);
close(pfd[1]);
close(pfd[0]);
fflush(stdout);
return 1;
}
read_from_file(&directory,octal);
if (write(pfd[1],256) < 0)
perror("Error writing dir to pipe");
if (write(pfd[1],sizeof(int)) < 0)
perror("error writing octal to pipe");
int r;
close(pfd[1]);
while (r = read(pfd[0],256)) {
if (r > 0) {
printf("%s",res);
}
}
close(pfd[0]);
while (wait(NULL) != -1 || errno != ECHILD);
}
解决方法
因为孩子明显达到了......
printf("client here\n");
...但似乎没有达到...
printf("client just read from pipe\n");
...我们可以假设它在两个 read()
调用之一上无限期地阻塞。在正确的时机下,这解释了为什么父级在自己的 read()
中阻塞管道。但是这种阻塞是如何以及为什么发生的?
您的程序中至少存在三个严重的语义错误:
-
管道不适用于双向通信。例如,一个进程有可能读回它自己写的并打算用于不同进程的字节。如果您想要双向通信,请使用两个管道。在您的情况下,我认为这可以避免明显的死锁,尽管它本身并不能使程序正常工作。
-
write
和read
不一定传输请求的全部字节数,并且短读和写不被认为是错误的。成功时,这些函数返回传输的字节数,如果您想确保传输特定数量的字节,则需要在循环中运行read
或write
,使用返回通过正在传输的缓冲区跟踪进度的值。或者改用fread()
和fwrite()
。 -
管道传送无差别的字节流。也就是说,它们不是面向消息的。假设从管道读取将与对管道的写入配对,因此每次读取都准确地接收一次写入写入的字节是不安全的。然而,您的代码取决于它的发生。
这是一个可能的失败场景,可以解释您的观察:
父母:
-
fork()
是孩子。 - 一段时间后对管道执行两次写入,一次来自变量
directory
,另一次来自变量octal
。至少其中的第一篇是一篇简短的文章。 - 关闭管道写入端的副本。
- 阻止尝试从管道中读取数据。
孩子:
- 读取所有通过第一次读取写入的字节(到它的
directory
副本)。 - 在第二个
read()
上阻塞。尽管父进程关闭了它的写端副本,它仍然可以这样做,因为管道的写端在子进程中仍然是打开的。
然后你就会陷入僵局。管道的两端至少在一个进程中打开,管道为空,并且两个进程都被阻塞,试图读取永远无法到达的字节。
还有其他可能性也大致相同,其中一些不依赖于简短的写入。
,父进程试图在子进程读取管道并将结果写入管道之前从管道中读取。使用两个不同的管道进行双向通信解决了这个问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。