在管道块上读取,直到在管道末端运行的程序终止 - Windows

Posted

技术标签:

【中文标题】在管道块上读取,直到在管道末端运行的程序终止 - Windows【英文标题】:read on a pipe blocks until program running at end of pipe terminates - Windows 【发布时间】:2020-01-20 17:54:04 【问题描述】:

我有一个示例程序,每秒输出一行文本。在下面的测试程序中,该程序将一些文本写入stdout,然后等待 1 秒并重复 20 次。

我有另一个程序使用popen(Windows 上的_popen)打开一个管道以从程序中读取。然后我使用fgets 读取数据。我遇到的问题是 fgets 阻塞直到程序终止。然后我一口气得到所有的输出,全部 20 行。我想一次输出一行,然后让fgets 阻塞直到下一行准备好。原因是我打算在一个将不断运行、输出文本的程序上使用它,例如喜欢使用tail

如果我在一个一次性输出一些文本并退出的程序上运行此代码示例,那么它可以正常工作。

为什么fgets 会屏蔽?测试程序确实会立即打印一些文本,那么fgets 为什么不立即读取第一行文本呢?

代码如下:

#include <stdio.h>
#include <windows.h>

void execute(const char* cmd) 
    char buffer[128] =  0 ;
    FILE* pipe = _popen(cmd, "r");

    if (!pipe) 
        printf("popen() failed!\n");
        return;
    

    while (!feof(pipe)) 
        if (fgets(buffer, 128, pipe) != nullptr)
            printf("%s", buffer);
    

    int rc = _pclose(pipe);

    if (rc != EXIT_SUCCESS)  // return code not 0
        printf("pclose exit failure: %d\n", rc);
    



int main(int argc, char* argv[]) 
    if (argc != 2) 
        printf("Usage: pipe_test.exe <program>\n");
        exit(1);
    

    execute(argv[1]);

程序运行,helloworld.exe:

#include <stdio.h>
#include <windows.h>

int main() 

    for (int i = 0; i < 20; i++) 
        printf("Hello World %d\n", i);
        Sleep(1000);
    

【问题讨论】:

默认情况下会缓冲写入管道。使用setvbuf() 关闭缓冲或在每个printf() 之后调用fflush(stdout) @Barmar 我试过 setvbuf(pipe, NULL, _IONBF, 128);创建管道后但没有效果 @Barmar 在被调用程序中 flush(stdout) 表示立即输出。不太方便,因为我更喜欢使用未经修改的调用程序,但给了我一个解决方法。我认为被调用程序中 printf 中的 \n 与 flush 相同? @AngusComber printf 将在换行符上刷新,但仅在进入真正的标准输出设备时才会刷新。如果它被重定向,那么它会被缓冲以提高吞吐量。 ***.com/questions/5431941/… 【参考方案1】:

为什么fgets 会屏蔽?

因为它在等待孩子们输出一些东西。

测试程序确实会立即打印一些文本,那么为什么fgets 不立即读取第一行文本呢?

它实际上立即打印文本。正如@Barmar 所指出的,这里的问题是写入管道被C 标准库实现缓冲(而不是line 缓冲)。这种缓冲发生在您的子程序 (helloworld) 中,而不是在您的父程序 (pipe_test) 中。

在您的父程序中,您无法控制通过popen() 生成的子程序将执行的操作,因此如果子输出像这种情况一样被缓冲,您唯一可以做的事情(不修改子程序代码)是等到缓冲区刷新到管道。

为了更快地得到输出,你必须修改孩子的代码来手动调用fflush()或使用setvbuf()来禁用缓冲:

int main() 
    setvbuf(stdout, NULL, _IONBF, 0); // Disable buffering on stdout.

    for (int i = 0; i < 20; i++) 
        printf("Hello World %d\n", i);
        Sleep(1000);
    

你真的无能为力了。

【讨论】:

以上是关于在管道块上读取,直到在管道末端运行的程序终止 - Windows的主要内容,如果未能解决你的问题,请参考以下文章

从管道读取时出错

Linux:打开命名管道进行写入时超时

在 bash 中打开命名管道,而不读取或写入它

使用输入管道和管理键盘

为啥在没有人阅读后继续写入命名管道?

两个进程之间使用的命名管道有啥问题?