Процесс ускорения непрерывно читает вывод - PullRequest
0 голосов
/ 16 мая 2018

Я пытаюсь прочитать результаты / журналы из разных процессов и отобразить их в графическом интерфейсе. Процессы будут работать в течение длительного времени и будут давать огромные результаты. Я планирую передавать результаты этих процессов и отображать их в соответствии с моими потребностями. Все это время позволяет моему графическому приложению принимать пользовательские данные и выполнять другие действия.

То, что я сделал здесь, из основного потока запускает два потока для каждого процесса. Один для запуска процесса, а другой для чтения результатов процесса.

Это решение, к которому я пришел до сих пор.

// 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

1 Ответ

0 голосов
/ 17 мая 2018

Наиболее яркими концептуальными проблемами, которые я вижу, являются

  1. Процесс асинхронный, нет необходимости добавлять поток для его запуска. ¹

  2. Вы преждевременно закрываете трубу:

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

    Выполнение не является «блокировкой» в том смысле, что оно не будет ждать выхода ребенка.Вы можете использовать wait для достижения этой цели.Кроме этого, вы можете просто удалить вызов close().

    При закрытии означает, что вы потеряете весь или часть вывода.Вы можете не увидеть ничего из вывода, если дочернему процессу потребуется некоторое время, прежде чем он выведет первые данные.

  3. Вы получаете доступ к mStream из нескольких потоков без синхронизации.Это вызывает неопределенное поведение , потому что оно открывает Data Race .

    . В этом случае вы можете устранить непосредственную проблему, удалив упомянутый ранее вызов mStream.close(), но выдолжен позаботиться о том, чтобы запустить поток чтения только после child был инициализирован.

    Строго говоря, такую ​​же осторожность следует соблюдать для std::cout.

  4. Вы передаете ссылку io_service, но она не используется.Просто отбросить это кажется хорошей идеей.

  5. Деструктор MyProcess должен отсоединить или присоединиться к потокам.Чтобы предотвратить появление зомби, он также должен отсоединить или пожать дочерний пид.

    В сочетании с временем жизни 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_service с асинхронным вводом-выводом , вместо этого вы могли бы даже покончить с целым потоком, начав с опроса обслуживание из цикла событий вашего GUI².

Если вам это нужно, и поскольку дочерние процессы естественным образом выполняются асинхронно3, вы можете просто сделать:

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 имеет готовую интеграцию ( Как интегрировать основной цикл Boost.Asio в среду GUI, такую ​​как Qt4 или GTK )

³ на всех платформах, поддерживаемых Boost Process

...