7

根据Boost 文档(“为什么管道不关闭?”部分),以下代码将导致死锁:

#include <boost/process.hpp>

#include <iostream>

namespace bp = ::boost::process;

int main(void)
{
  bp::ipstream is;
  bp::child c("ls", bp::std_out > is);

  std::string line;
  while (std::getline(is, line))
  {
    std::cout << line << "\n";
  }

  return 0;
}

文档说:

这也会死锁,因为子进程退出时管道不会关闭。因此,即使进程结束,ipstream 仍会查找数据。

但是,我无法重现死锁(在 Linux 下)。此外,我不明白为什么会首先发生死锁。一旦子进程退出,它就会关闭管道的写端。管道的读取端仍然可供父进程读取,并且std::getline()一旦管道缓冲区中没有更多数据可用并且写入端关闭,它就会失败,对吗?如果在子进程执行期间管道缓冲区填满,子进程将阻塞等待父进程从管道中读取足够的数据,以便它可以继续。

那么万一上面的代码会死锁,有没有简单的方法可以重现死锁场景呢?

更新:

事实上,下面这段代码使用 Boost 进程死锁:

#include <boost/process.hpp>
#include <iostream>

namespace bp = ::boost::process;

int main() 
{
    bp::ipstream is;
    bp::child c("/bin/bash", bp::args({"-c", "ls >&40"}), bp::posix::fd.bind(40, is.rdbuf()->pipe().native_sink()));

    std::string line;
    while (std::getline(is, line))
    {
        std::cout << line << "\n";
    }

    c.wait();

    return 0;
}

我想知道这是否真的是 Linux 下进程生成的一些不可避免的属性。使用Facebook 的Folly库中的Subprocess复制上述示例至少不会死锁:

#include <folly/Subprocess.h>
#include <iostream>

int main()
{
   std::vector<std::string> arguments = {"/bin/bash", "-c", "ls >&40"};

   folly::Subprocess::Options options;
   options.fd(40, STDOUT_FILENO);

   folly::Subprocess p(arguments, options);
   std::cout << p.communicate().first;
   p.wait();

   return 0;
}
4

1 回答 1

3

一旦子进程退出,它就会关闭管道的写端。

这似乎是假设。什么程序关闭什么管道?

如果/bin/ls有,会发生什么

bp::child c("/bin/bash", bp::args({"-c", "ls; ls"}));

如果ls真的关闭它,那么它应该关闭两次。

也许 bash 复制了引擎盖下的句柄,因此子进程关闭了同一管道的不同副本。我不确定这些语义的可靠性¹

因此,显然 stdout 很适合。但是,当在 linux 上使用非标准文件描述符进行输出时,我可以重现死锁:

#include <boost/process.hpp>
#include <iostream>

namespace bp = ::boost::process;

int main() {
    bp::ipstream is;
    bp::child c("/bin/bash", bp::args({"-c", "exec >&40; ls"}), bp::posix::fd.bind(40, is.rdbuf()->pipe().native_sink()));

    std::string line;
    while (std::getline(is, line)) {
        std::cout << line << "\n";
    }
}

我不确定为什么 bash 中子进程的“关闭标准输出”行为在重定向到 fd 时应该表现不同,但是你去了。

演示相关死锁的另一种好方法是:

{
    bp::child c("/bin/bash", bp::args({"-c", "ls -R /"}), bp::std_out > is);
    c.wait();
    return c.exit_code();
}

这个答案不是决定性的,但确实观察到了一些要点并在 linux 上进行了演示:

  • 并非所有文件描述符似乎都与标准输出相同
  • 在子进程异步处理其 IO 但调用进程同步处理它们的许多情况下,可能会发生死锁。

我认为后者是文档中的重点。


¹ 实际上,文档明确表明这些语义的差异是 Win32 中的问题:

在这个库中不能使用自动管道关闭,因为管道可能是一个文件句柄(对于 Windows 上的异步管道)

于 2017-08-22T00:39:19.843 回答