提升进程持续读取输出

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

目前程序的工作情况符合预期。但我不确定这是否是正确的解决方案。如果有其他替代/更好/正确的解决方案,请告诉我。

谢谢, Surya


“按预期工作”的可能性非常小,因为它完全充满了语法(和其他)错误。” - sehe
我理解有语法错误。我的问题是逻辑是否正确。我会尝试提供一个最小化可运行示例(MWE)。 - Surya
好的。你可以修复它。我已经修复了,但那时我已经花费了大部分想象的时间并转向了下一个问题。发布懒散的代码只会伤害你的问题。 - sehe
1个回答

2
我看到最引人注目的概念问题是:
  1. Process are asynchronous, no need to add a thread to run them.¹

  2. You prematurely close the pipe:

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

    Run is not "blocking" in the sense that it will not wait for the child to exit. You could use wait to achieve that. Other than that, you can just remove the close() call.

    With the close means you will lose all or part of the output. You might not see any of the output if the child process takes a while before it outputs the first data.

  3. You are accessing the mStream from multiple threads without synchronization. This invokes Undefined Behaviour because it opens a Data Race.

    In this case you can remove the immediate problem by removing the mStream.close() call mentioned before, but you must take care to start the reader-thread only after the child has been initialized.

    Strictly speaking the same caution should be taken for std::cout.

  4. You are passing the io_service reference, but it's not being used. Just dropping it seems like a good idea.

  5. The destructor of MyProcess needs to detach or join the threads. To prevent Zombies, it needs to detach or reap the child pid too.

    In combination with the lifetime of mStream detaching the reader thread is not really an option, as mStream is being used from the thread.

让我们先解决第一个问题,之后我将建议显示更多的简化内容,这些内容在您的示例范围内有意义。

首要修复

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

在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();
}

简化!

在当前模型中,线程没有发挥它的作用。

如果你使用异步IOio_service,甚至可以通过在GUI事件循环²中轮询服务来摆脱整个线程。

如果你打算这样做,而且由于子进程自然地异步执行³,你可以简单地执行以下操作:

在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有一个现成的集成方案(如何在像Qt4或GTK这样的GUI框架中集成Boost.Asio主循环)。

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


请修复您的示例,它非常误导人。根据文档所述,“如果在nm退出后尝试读取,则管道将导致死锁”。 - Yuki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接