单io_context运行多进程遇超时问题:为何所有任务均超时?
问题分析:单io_context下进程任务全部超时的原因
我修改了某个示例,改用main函数中定义的单个io_context对象,在多线程任务中运行所有进程。原本预期所有任务都能成功完成(因为任务耗时应该小于设置的20ms超时),但实际所有任务都超时了,这是什么原因?
#include <boost/asio.hpp> #include <boost/process.hpp> #include <iostream> using duration = std::chrono::system_clock::duration; namespace asio = boost::asio; using namespace std::chrono_literals; std::string ExecuteProcess(boost::filesystem::path exe, std::vector<std::string> args, // duration time, // std::error_code& ec, asio::io_context& ioc) { namespace bp = boost::process; std::future<std::string> data, err_output; bp::group g; bp::child child(exe, args, ioc, g, bp::error(ec), bp::std_in.null(), // bp::std_out > data, // bp::std_err > err_output); if (std::error_code ignore; child.running()) { g.terminate(ignore); } if (data.wait_for(time) == std::future_status::ready) { ec.clear(); return data.get(); } ec = make_error_code(asio::error::timed_out); return {}; } int main() { constexpr duration timeout = 20ms; constexpr auto script2 = "sleep 0.00005; echo -n 'Hello world'"; constexpr auto script1 = "/usr/bin/curl http://httpbin.org/ip -m 5"; asio::io_context ioc; boost::asio::detail::thread_group collect_threads; for (int i = 0 ; i < 20 ; i++) { collect_threads.create_thread([&]() { std::error_code ec_; auto s = ExecuteProcess("/bin/bash", {"-c", script2}, timeout, ec_, ioc); std::cout << "got " << ec_.message() << ": " << s << std::endl; }); } ioc.run(); }
核心原因:子进程被提前终止
看ExecuteProcess里这段致命代码:
if (std::error_code ignore; child.running()) { g.terminate(ignore); }
这行代码在子进程刚创建完成后,立刻检查进程是否在运行,只要是就直接终止整个进程组。子进程刚启动还没来得及执行脚本内容就被杀死了,导致关联的std::future永远不会就绪,后续data.wait_for(time)必然超时。
之所以会触发这个逻辑,是因为子进程启动需要极短的时间窗口,child.running()在创建后几乎一定会返回true。
修复方案
直接删掉这段错误的终止代码,子进程就能正常执行完成,std::future会在脚本输出后就绪,就能正确获取到结果。
关于waitpid与child.exit_code()返回383的疑问
child.exit_code()返回383(十六进制0x17F),本质是因为进程还没终止。Boost Process的exit_code()只有在进程完全退出并被回收后,才会返回有效的退出码。若用waitpid(pid, &status, WNOHANG)实现轮询,注意以下两点:
WNOHANG是非阻塞调用,若进程仍在运行,waitpid会返回0,此时不能调用child.exit_code(),进程状态还未更新。- 必须等
waitpid返回大于0的pid(表示进程已退出)或返回-1(出错)后,再调用child.exit_code()才能得到正确值。
另外,更推荐用Boost Process的异步接口配合io_context定时器实现超时,这更符合Asio的异步设计模式,比手动调用waitpid更简洁可靠。
内容的提问来源于stack exchange,提问作者Zohar81
相关产品推荐
相关产品推荐

