Read child process stdout in a separate thread wit

2020-04-20 06:23发布

问题:

I have a main program that uses boost process library to spawn a child process that prints

Hello World !

on its stdout every 5 seconds.

I would like to read/monitor the stdout of the child process in the main process when it becomes available along with performing other operations within the main program.

I have tried out the examples for boost asynchronous IO (http://www.boost.org/doc/libs/1_66_0/doc/html/boost_process/tutorial.html) but all these seem to block the main program until the child process has exited.

Do we need to read the childs stdout in a separate thread ? Can someone please provide an example where the main program can do other things at the same time instead of blocking for stdout from th child ?

回答1:

I have tried out the examples for boost asynchronous IO (http://www.boost.org/doc/libs/1_66_0/doc/html/boost_process/tutorial.html) but all these seem to block the main program until the child process has exited.

Look again. All the samples under Asynchronous I/O should help you select a method that works for you.

Do we need to read the childs stdout in a separate thread ? Can someone please provide an example where the main program can do other things at the same time instead of blocking for stdout from th child ?

No, you don't need to. Though you can and depending on what you're trying to achieve, it might be the easiest thing to do.

Synchronous

You failed to tell us what you want to be able to do, so let's assume you just want the output printed:

Live On Coliru:

bp::child c("/bin/bash", std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World !'; done" });
c.wait();

That's synchronous, so you can't do work in the meantime

Using a Reader Thread

That's like:

Live On Coliru:

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

namespace bp = boost::process;

int main() {
    bp::ipstream output;
    std::thread reader([&output] {
        std::string line;
        while (std::getline(output, line))
            std::cout << "Received: '" << line << "'" << std::endl;
    });

    bp::child c("/bin/bash",
        std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World ('$a')!'; done" },
        bp::std_out > output);

    while (c.running()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(2793));
        std::cout << "(main thread working)" << std::endl;
    }

    std::cout << "(done)" << std::endl;
    c.wait();

    output.pipe().close();
    reader.join();
}

Prints (Live On Coliru):

Received: 'Hello World (1)!'
(main thread working)
Received: 'Hello World (2)!'
(main thread working)
Received: 'Hello World (3)!'
Received: 'Hello World (4)!'
(main thread working)
Received: 'Hello World (5)!'
(main thread working)
Received: 'Hello World (6)!'
(main thread working)
Received: 'Hello World (7)!'
Received: 'Hello World (8)!'
(main thread working)
Received: 'Hello World (9)!'
(main thread working)
Received: 'Hello World (10)!'
(main thread working)
(done)

Asynchronous IO

Using no threads (well, just the main thread), could look like:

Live On Coliru

#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
#include <iomanip>

namespace bp = boost::process;

struct OtherWork {
    using clock = std::chrono::high_resolution_clock;

    OtherWork(boost::asio::io_context& io) : timer(io) { }

    void start() {
        timer.expires_at(clock::time_point::max());
        loop();
    }

    void stop() {
        timer.expires_at(clock::time_point::min());
    }

  private:
    void loop() {
        if (timer.expires_at() == clock::time_point::min()) {
            std::cout << "(done)" << std::endl;
            return;
        }

        timer.expires_from_now(std::chrono::milliseconds(2793));
        timer.async_wait([=](boost::system::error_code ec) {
            if (!ec) {
                std::cout << "(other work in progress)" << std::endl;
                start();
            } else {
                std::cout << "(" << ec.message() << ")" << std::endl;
            }
        });
    }

    boost::asio::high_resolution_timer timer;
};

int main() {
    boost::asio::io_context io;
    bp::async_pipe output(io);

    OtherWork mainwork{io};

    bp::child c("/bin/bash", std::vector<std::string> { "-c", "for a in {1..10}; do sleep 2; echo 'Hello World ('$a')!'; done" },
            bp::std_out > output, io, bp::on_exit([&mainwork,&output](auto...) {
                    output.close();
                    mainwork.stop();
                }));

    std::function<void()> readloop = [&,buffer=std::array<char, 32>{}]() mutable {
        output.async_read_some(bp::buffer(buffer), [&](boost::system::error_code ec, size_t transferred) {
                if (transferred) {
                    std::cout << "Received: '";
                    while (transferred && buffer[transferred-1] == '\n') // strip newline(s)
                        --transferred;
                    std::cout.write(buffer.data(), transferred);
                    std::cout << "'" << std::endl;
                }

                if (ec)
                    std::cout << "Output pipe: " << ec.message() << std::endl;
                else
                    readloop();
            });
    };

    mainwork.start();
    readloop();
    io.run();
}

Prints Live On Coliru

Received: 'Hello World (1)!'
(other work in progress)
Received: 'Hello World (2)!'
(other work in progress)
Received: 'Hello World (3)!'
Received: 'Hello World (4)!'
(other work in progress)
Received: 'Hello World (5)!'
(other work in progress)
Received: 'Hello World (6)!'
(other work in progress)
Received: 'Hello World (7)!'
Received: 'Hello World (8)!'
(other work in progress)
Received: 'Hello World (9)!'
(other work in progress)
Received: 'Hello World (10)!'
Output pipe: End of file
Child exited with code=0(Success)
(Operation canceled)