使用Boost async_pipe跨fork传递子进程输出时read_loop无法终止的问题
问题
刚接触boost::asio和boost::process库,遇到了一个难以解决的问题:
编写的测试程序流程如下:
- 调用
fork()分为父进程和子进程分支; - 子进程分支使用
boost::process::child类在异步环境中执行Unix命令ls; - 子进程将
boost::process::async_pipe传入该类,将std_out输出到管道; - 父进程分支希望逐行读取管道内容并进一步处理。
目前实现能正常输出内容,但父进程中的read_loop()调用无法终止,仿佛永远没到达EOF或被阻塞。以下是最小可复现示例(MWE):
#include <boost/process.hpp> #include <boost/asio.hpp> #include <iostream> #include <string> #include <unistd.h> void read_loop(boost::process::async_pipe& pipe) { static boost::asio::streambuf buffer; boost::asio::async_read_until( pipe, buffer, '\n', [&](boost::system::error_code error_code, std::size_t bytes) { if (!error_code) { std::istream is(&buffer); if (std::string line; std::getline(is, line)) { std::cout << "Read Line: " << line << "\n"; } read_loop(pipe); } else { std::cout << "Error in read_loop()!\n"; pipe.close(); } } ); } int main(int argc, char* argv[]) { boost::asio::io_context io_context{}; boost::process::async_pipe pipe{ io_context }; io_context.notify_fork(boost::asio::io_context::fork_prepare); pid_t pid{ fork() }; if (pid == 0) { io_context.notify_fork(boost::asio::io_context::fork_child); boost::process::child child( boost::process::args({ "/usr/bin/ls", "/etc/" }), boost::process::std_out > pipe, boost::process::on_exit([&](int exit, std::error_code error_code) { std::cout << "[Exited with code " << exit << " (" << error_code.message() << ")]\n"; }), io_context ); io_context.run(); } else { io_context.notify_fork(boost::asio::io_context::fork_parent); read_loop(pipe); io_context.run(); } return 0; }
程序能输出预期内容,但最后会挂起直到被强制终止。请问为何read_loop()函数会阻塞/无法正常退出?
原因分析
核心问题在于管道写端的引用计数未清零:
fork()会复制所有文件描述符,因此父进程和子进程各自持有async_pipe的写端句柄;- 子进程中
ls命令执行完毕后,boost::process::child会关闭自身持有的写端,但父进程的写端副本仍然处于打开状态; - 管道的读端只有在所有写端都关闭时,才会触发EOF(
boost::asio::error::eof)信号; - 父进程的
read_loop()一直在等待数据,永远收不到EOF,导致io_context.run()无法退出,程序挂起。
此外代码还有两处细节问题:
read_loop中的static streambuf属于全局状态,会引发意外的内存残留问题;- 错误处理分支未区分EOF和真正的错误,将正常结束信号误判为错误。
解决方案
1. 父进程主动关闭管道写端
在父进程分支中,明确关闭async_pipe的写端,确保子进程写端关闭后,管道没有剩余写端:
else { io_context.notify_fork(boost::asio::io_context::fork_parent); // 关闭父进程持有的管道写端 pipe.close(boost::asio::write_side); read_loop(pipe); io_context.run(); }
2. 修正read_loop的错误逻辑和静态buffer
将静态streambuf改为局部共享对象,同时区分EOF和错误场景:
void read_loop(boost::process::async_pipe& pipe) { auto buffer = std::make_shared<boost::asio::streambuf>(); boost::asio::async_read_until( pipe, *buffer, '\n', [&, buffer](boost::system::error_code error_code, std::size_t bytes) { if (!error_code) { std::istream is(buffer.get()); std::string line; if (std::getline(is, line)) { std::cout << "Read Line: " << line << "\n"; } read_loop(pipe); } else if (error_code == boost::asio::error::eof) { std::cout << "Read completed (EOF)\n"; pipe.close(); } else { std::cout << "Error in read_loop(): " << error_code.message() << "\n"; pipe.close(); } } ); }
3. 可选:子进程关闭管道读端
子进程不需要读取管道,可主动关闭读端减少资源占用:
if (pid == 0) { io_context.notify_fork(boost::asio::io_context::fork_child); pipe.close(boost::asio::read_side); // 关闭子进程的读端 boost::process::child child( boost::process::args({ "/usr/bin/ls", "/etc/" }), boost::process::std_out > pipe, boost::process::on_exit([&](int exit, std::error_code error_code) { std::cout << "[Exited with code " << exit << " (" << error_code.message() << ")]\n"; }), io_context ); io_context.run(); }
内容的提问来源于stack exchange,提问作者Matryka
相关产品推荐
相关产品推荐

