Boost流程不断读取输出

时间:2018-05-16 07:32:00

标签: boost stdthread asio boost-process

我正在尝试从不同进程读取输出/日志并在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();
}

该计划目前正按预期运作。但我不确定这是否是正确的解决方案。如果有任何其他/更好/正确的解决方案,请告诉我

谢谢, 苏里亚

1 个答案:

答案 0 :(得分:0)

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

  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的析构函数需要分离或加入线程。为了防止僵尸,它也需要分离或收获孩子的pid。

    mStream的生命周期相结合,分离阅读器线程实际上不是一个选项,因为线程正在使用mStream

  6. 让我们首先推出第一个修复程序,之后我会建议显示一些在样本范围内有意义的简化。

    第一次修复

    我使用一个简单的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_service代替asynchronous IO,您甚至可以通过polling来自GUI事件循环内部的服务来取消整个线程。< / p>

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

    <强> 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支持的所有平台上

    ³