为什么使用 popen() 读取时会出现内置延迟?

Why is there a built-in delay when reading with popen()?

我正在通过 popen() 执行长 运行(并且经常被阻止)命令:"ls -R /"

问题:popen() 读入您提供的缓冲区,它似乎试图在返回之前填充整个缓冲区。这会导致它经常阻塞(如果您的缓冲区很大)。

解决方案似乎是使底层 fd 成为非阻塞的。当我这样做时,popen() 仍然会阻塞,通常每次大约 1 秒。为什么会这样?

这是我的代码。确保使用 -std=c++11:

进行编译
#include <cstdio>
#include <iostream>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>

static constexpr size_t SIZE = 65536;

struct Time
{
   friend std::ostream &operator<<(std::ostream &os, Time const &t)
   {
      (void)t;

      timeval tv;
      gettimeofday(&tv, nullptr);

      os << tv.tv_sec << "." << std::fixed << tv.tv_usec << " ";
      return os;
   }
};

int main()
{
   FILE *file;
   file = popen("ls -R /", "r");
   if(!file)
   {
      std::cerr << "Could not open app: " << errno;
      return -1;
   }

   // Make it non-blocking
   int fd = fileno(file);
   fcntl(fd, F_SETFL, O_NONBLOCK);

   char buffer[SIZE];
   Time t;
   while(true)
   {
      int rc = fread(buffer, 1, SIZE, file);
      if(rc <= 0)
      {
         if(EAGAIN == errno)
         {
            usleep(10);
            continue;
         }
         std::cerr << t << "Error reading: " << errno << std::endl;
         break;
      }
      std::cerr << t << "Read " << rc << std::endl;
   }
   pclose(file);
   return 0;
}

输出(注意它们相隔大约 1 秒,即使 fd 是非阻塞的并且我在循环中只有 10 毫秒的停顿):

1429625100.983786 Read 4096
1429625101.745369 Read 4096
1429625102.426967 Read 4096
1429625103.185273 Read 4096
1429625103.834241 Read 4096
1429625104.512131 Read 4096
1429625105.188010 Read 4096
1429625105.942257 Read 4096
1429625106.642877 Read 4096

首先,您应该使用 read 而不是 fread。 stdio 函数在 OS 之外有自己的缓冲层,因此它们甚至可以在非阻塞文件描述符上阻塞。使用 read 来避免这种情况。

其次,您需要停止 ls 缓冲其输出。 default behavior for programs that link to glibc 是在 stdout 连接到 TTY 时使用行缓冲,在连接到管道或重定向到文件时使用全缓冲。全缓冲意味着只有当 4KB 缓冲区填满时才刷新输出,而不是每次输出换行时刷新。

您可以使用 stdbuf 覆盖此行为。请注意,它仅适用于使用 C 流和 link 到 glibc 的程序。这是大多数程序,但不是全部。

popen("stdbuf -oL ls -R /", "r");