Наиболее яркими концептуальными проблемами, которые я вижу, являются
Процесс асинхронный, нет необходимости добавлять поток для его запуска. ¹
Вы преждевременно закрываете трубу:
mService.run();
mStream.pipe().close();
Выполнение не является «блокировкой» в том смысле, что оно не будет ждать выхода ребенка.Вы можете использовать wait
для достижения этой цели.Кроме этого, вы можете просто удалить вызов close()
.
При закрытии означает, что вы потеряете весь или часть вывода.Вы можете не увидеть ничего из вывода, если дочернему процессу потребуется некоторое время, прежде чем он выведет первые данные.
Вы получаете доступ к mStream
из нескольких потоков без синхронизации.Это вызывает неопределенное поведение , потому что оно открывает Data Race .
. В этом случае вы можете устранить непосредственную проблему, удалив упомянутый ранее вызов mStream.close()
, но выдолжен позаботиться о том, чтобы запустить поток чтения только после child
был инициализирован.
Строго говоря, такую же осторожность следует соблюдать для std::cout
.
Вы передаете ссылку io_service
, но она не используется.Просто отбросить это кажется хорошей идеей.
Деструктор 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