【问题标题】:Boost process continuously read outputBoost进程连续读取输出
【发布时间】:2018-05-16 07:32:00
【问题描述】:

我正在尝试读取来自不同进程的输出/日志并将它们显示在 GUI 中。这些过程将运行很长时间并产生巨大的输出。我计划流式传输这些流程的输出并根据我的需要显示它们。同时允许我的 gui 应用程序接受用户输入并执行其他操作。

我在这里所做的是,从主线程为每个进程启动两个线程。一个用于启动进程,另一个用于读取进程的输出。

这是我迄今为止提出的解决方案。

// Process Class
class MyProcess {
namespace bp = boost::process;
boost::asio::io_service mService; // member variable of the class
bp::ipstream mStream // member variable of the class
std::thread mProcessThread, mReaderThread // member variables of the class.

public void launch();
};

void
MyProcess::launch()
{
mReaderThread = std::thread([&](){
std::string line;
while(getline(mStream, line)) {
std::cout << line << std::endl;
}
});

mProcessThread = std::thread([&]() {
auto c = boost::child ("/path/of/executable", bp::std_out > mStream, mService);

mService.run();
mStream.pipe().close();
}
}


// Main Gui class
class MyGui
{
MyProcess process;
void launchProcess();
}

MyGui::launchProcess()
{
process.launch();
doSomethingElse();
}

到目前为止,该程序正在按预期运行。但我不确定这是否是正确的解决方案。请让我知道是否有任何替代/更好/正确的解决方案

谢谢, 苏里亚

【问题讨论】:

  • “按预期”工作的机会非常小,因为它完全充满了语法(和其他)错误。
  • 我了解存在语法错误。我的问题是逻辑是否有效。我会尝试放一个 MWE。
  • 好吧。你可以修复它。 I already had,但到那时我大部分时间都花在了我喜欢的所有时间上,然后转向next question。发布草率的代码只会伤害您的问题。

标签: boost stdthread asio boost-process


【解决方案1】:

我看到的最引人注目的概念问题是

  1. 进程是异步的,无需添加线程来运行它们。¹

  2. 你过早地关闭了管道:

    mService.run();
    mStream.pipe().close();
    

    在它不会等待孩子退出的意义上,运行并不是“阻塞”。您可以使用wait 来实现这一点。除此之外,您可以删除 close() 调用。

    关闭意味着您将丢失全部或部分输出。如果子进程在输出第一个数据之前需要一段时间,您可能看不到任何输出。

  3. 您正在从多个线程访问mStream 而没有同步。这会调用Undefined Behaviour,因为它会打开一个Data Race

    在这种情况下,您可以通过删除前面提到的mStream.close() 调用来解决直接问题,但您必须注意仅在child 初始化之后启动阅读器线程。

    严格来说,std::cout 应该同样谨慎。

  4. 您正在传递 io_service 引用,但它没有被使用。放弃它似乎是个好主意。

  5. MyProcess 的析构函数需要分离或加入线程。为了防止 Zombies,它也需要分离或获取子 pid。

    结合mStream 的生命周期,分离阅读器线程并不是一个真正的选择,因为正在从线程中使用mStream

让我们首先发布第一个修复程序,然后我会建议在您的示例范围内显示一些更有意义的简化。

第一次修复

我使用了一个简单的 bash 命令来模拟一个生成 1000 行 ping 的命令:

Live On Coliru

#include <boost/process.hpp>
#include <thread>
#include <iostream>
namespace bp = boost::process;

/////////////////////////
class MyProcess {
    bp::ipstream mStream;
    bp::child mChild;
    std::thread mReaderThread;

  public:
    ~MyProcess();
    void launch();
};

void MyProcess::launch() {
    mChild = bp::child("/bin/bash", std::vector<std::string> {"-c", "yes ping | head -n 1000" }, bp::std_out > mStream);

    mReaderThread = std::thread([&]() {
        std::string line;
        while (getline(mStream, line)) {
            std::cout << line << std::endl;
        }
    });
}

MyProcess::~MyProcess() {
    if (mReaderThread.joinable()) mReaderThread.join();
    if (mChild.running()) mChild.wait();
}

/////////////////////////
class MyGui {
    MyProcess _process;
  public:
    void launchProcess();
};

void MyGui::launchProcess() {
    _process.launch();
    // doSomethingElse();
}

int main() {
    MyGui gui;
    gui.launchProcess();
}

简化!

在当前模型中,线程不会承受它的重量。

如果您将io_serviceasynchronous IO 一起使用,您甚至可以从GUI 事件循环内部的polling 服务开始取消整个线程²。

如果你想要它,并且由于子进程自然地异步执行³,你可以简单地这样做:

Live On Coliru

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

std::thread launch(std::string const& command, std::vector<std::string> args = {}) {
    namespace bp = boost::process;

    return std::thread([=] {
        bp::ipstream stream;
        bp::child c(command, args, bp::std_out > stream);

        std::string line;
        while (getline(stream, line)) {
            // TODO likely post to some kind of queue for processing
            std::cout << line << std::endl;
        }

        c.wait(); // reap PID
    });
}

演示显示的输出与之前完全相同。


¹其实加线程就是在自找麻烦fork

² 或者可能是空闲滴答声或类似的想法。 Qt 有现成的集成 (How to integrate Boost.Asio main loop in GUI framework like Qt4 or GTK)

³ 在 Boost Process 支持的所有平台上

【讨论】:

  • 来自doc: '如果你在 nm 退出后尝试读取,管道会导致死锁',请修正你的例子,它非常具有误导性。
猜你喜欢
  • 1970-01-01
  • 2022-11-05
  • 2012-10-28
  • 2018-08-17
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 2020-11-12
  • 2021-08-03
相关资源
最近更新 更多