Сигналы и потоки - хорошее или плохое решение для дизайна?
-
26-09-2019 - |
Вопрос
Я должен написать программу, которая выполняет высокомешительно интенсивные расчеты. Программа может работать в течение нескольких дней. Расчет может быть легко отделен в разных потоках без необходимости общих данных. Я хочу GUI или веб-сервис, который сообщает мне о текущем состоянии.
Мой нынешний дизайн использует Boost :: Engles2 и Boost :: Threade. Он компилируется и уже работает как ожидалось. Если нить завершила одну итерацию и новые данные, доступна, она вызывает сигнал, который подключен к слоту в классе GUI.
Мои вопросы):
- Это сочетание сигналов и потоков мудрые идеи? Я еще один форум кто-то посоветовал кому-то еще не «идти по этой дороге».
- Есть ли потенциальные смертельные ловушки поблизости, что я не смог увидеть?
- Является ли мое ожидание реалистично, что будет «легко» использовать мой класс GUI для предоставления веб-интерфейса или QT, VTK или любое окно?
- Есть ли более умная альтернатива (как и другие Boost Libs), которые я упускаю из виду?
следующий код компилирует с
g++ -Wall -o main -lboost_thread-mt <filename>.cpp
Следующая код:
#include <boost/signals2.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <iterator>
#include <string>
using std::cout;
using std::cerr;
using std::string;
/**
* Called when a CalcThread finished a new bunch of data.
*/
boost::signals2::signal<void(string)> signal_new_data;
/**
* The whole data will be stored here.
*/
class DataCollector
{
typedef boost::mutex::scoped_lock scoped_lock;
boost::mutex mutex;
public:
/**
* Called by CalcThreads call the to store their data.
*/
void push(const string &s, const string &caller_name)
{
scoped_lock lock(mutex);
_data.push_back(s);
signal_new_data(caller_name);
}
/**
* Output everything collected so far to std::out.
*/
void out()
{
typedef std::vector<string>::const_iterator iter;
for (iter i = _data.begin(); i != _data.end(); ++i)
cout << " " << *i << "\n";
}
private:
std::vector<string> _data;
};
/**
* Several of those can calculate stuff.
* No data sharing needed.
*/
struct CalcThread
{
CalcThread(string name, DataCollector &datcol) :
_name(name), _datcol(datcol)
{
}
/**
* Expensive algorithms will be implemented here.
* @param num_results how many data sets are to be calculated by this thread.
*/
void operator()(int num_results)
{
for (int i = 1; i <= num_results; ++i)
{
std::stringstream s;
s << "[";
if (i == num_results)
s << "LAST ";
s << "DATA " << i << " from thread " << _name << "]";
_datcol.push(s.str(), _name);
}
}
private:
string _name;
DataCollector &_datcol;
};
/**
* Maybe some VTK or QT or both will be used someday.
*/
class GuiClass
{
public:
GuiClass(DataCollector &datcol) :
_datcol(datcol)
{
}
/**
* If the GUI wants to present or at least count the data collected so far.
* @param caller_name is the name of the thread whose data is new.
*/
void slot_data_changed(string caller_name) const
{
cout << "GuiClass knows: new data from " << caller_name << std::endl;
}
private:
DataCollector & _datcol;
};
int main()
{
DataCollector datcol;
GuiClass mc(datcol);
signal_new_data.connect(boost::bind(&GuiClass::slot_data_changed, &mc, _1));
CalcThread r1("A", datcol), r2("B", datcol), r3("C", datcol), r4("D",
datcol), r5("E", datcol);
boost::thread t1(r1, 3);
boost::thread t2(r2, 1);
boost::thread t3(r3, 2);
boost::thread t4(r4, 2);
boost::thread t5(r5, 3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
datcol.out();
cout << "\nDone" << std::endl;
return 0;
}
Решение
Это сочетание сигналов и потоков мудрые идеи? Я еще один форум кто-то посоветовал кому-то еще не «идти по этой дороге».
Кажется, это звучит. Можете ли вы предоставить ссылку на другой поток? Разве они объясняли свои рассуждения?
Есть ли потенциальные смертельные ловушки поблизости, что я не смог увидеть?
Если бы они не смогу их видеть. Что вам нужно позаботиться, состоит в том, что уведомления являются потоком безопасными (запуск сигнала не переключает контексты потока, к вашему GuiClass::slot_data_changed
следует вызывать из всех других потоков.
Является ли мое ожидание реалистично, что будет «легко» использовать мой класс GUI для предоставления веб-интерфейса или QT, VTK или любое окно?
Это не будет легко. Чтобы исправить это, вам придется сделать ваше уведомление об изменении потоковых контекстов. Вот что я бы сделал:
Иметь свой GuiClass
Будьте абстрактного базового класса, внедряя его собственное очередь сообщений. Когда GuiClass::slot_data_changed
называется вашими потоками, вы блокируете Mutex и опубликую копию полученного уведомления о внутреннем (private:
) очередь сообщений. В потоке GuiClass
Вы создаете функцию, которая блокирует MUTEX и ищет уведомления в очереди. Эта функция должна работать в потоке клиентского кода (в потоке бетонных классов, которые вы специализируетесь от абстрактных GuiClass
).
Преимущества:
- Ваш базовый класс инкапсулирует и изолирует переключение контекста потока, прозрачно к его специализациям.
Недостатки:
Ваш клиентский код должен либо запускать метод опроса или позволить ему работать (как функция обработки потоков).
Это немного сложно :)
Является ли мое ожидание реалистично, что будет «легко» использовать мой класс GUI для предоставления веб-интерфейса или QT, VTK или любое окно?
Так не видит, но это не так просто. Помимо потока контекстно-коммутации могут быть другие проблемы, которые я не пропускаю в данный момент.
Есть ли более умная альтернатива (как и другие Boost Libs), которые я упускаю из виду?
Не другие Boost Libs, но то, как вы написали свои темы, не хороши: присоединения выполнены последовательно в вашем коде. Иметь только один join
Для всех ниток используйте Boost :: Thread_group.
Вместо:
boost::thread t1(r1, 3);
boost::thread t2(r2, 1);
boost::thread t3(r3, 2);
boost::thread t4(r4, 2);
boost::thread t5(r5, 3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
У вас будет:
boost::thread_group processors;
processors.create_thread(r1, 3);
// the other threads here
processors.join_all();
Редактировать: Контекст нити - это все, что специфично для конкретной беговой резьбы (хранилище для резьбы, стопку этой резьбы, любые исключения, брошенные в контексте этого потока и т. Д.).
Когда у вас есть различные потоковые контексты в том же приложении (несколько потоков), вам необходимо синхронизировать доступ к ресурсам, созданным в контексте потока и доступа к разным потокам (с помощью блокировки примитивов).
Например, скажем, у вас есть a
, экземпляр class A
работает в потоке TA], делая некоторые вещи и b
, экземпляр class B
работает в контексте потока Tb] и b
хочет сказать a
что-нибудь.
"Хочет сказать a
что-то «часть означает, что b
хочет позвонить a.something()
и a.something()
будет вызываться в контексте туберкулеза (на стопку нити b).
Изменить это (иметь a.something()
бегать в контексте TA), вы должны Переключите контекст резьбы. Отказ Это означает, что вместо b
рассказывать a
"бежать A::something()
", b
рассказывает a
«Запустите :: что-то ()` В вашем собственном контексте потока ».
Классические этапы реализации:
b
отправляет сообщение наa
изнутри ...a
Опросы для сообщений из в пределах TAКогда
a
находит сообщение отb
, он проходит A.Something () сама в пределах ТА.
Это переключение потоковых контекстов (выполнение A::something
будет выполнен в ТП вместо туберкулеза, так как было бы, если бы назвали напрямую из b
).
Из ссылки, которую вы предоставили, кажется, это уже реализовано boost::asio::io_service
, Так что, если вы используете это, вам не нужно реализовать его самостоятельно.
Другие советы
Существует одна очень важная лопасть:
Насколько я понимаю Безопасность потоков сигналов2 Слоты бегут в сигнализация нить. Большинство библиотек GUI (особенно Qt и OpenGL) должен Сделайте все рисунку из одной нити. Это не проблема в целом, но требуется немного забота. У вас есть два варианта:
Во-первых, вы осторожны, что вы не делаете чертежа внутри GuiClass::slot_data_changed
(Поскольку вы используете Qt, посмотрите на QCOReApplication :: PostEvent (извините, не разрешено публиковать ссылку на Qt Docu)).
Во-вторых, вы создаете очередь сообщений самостоятельно, что сохраняет вызовы слотов и выполняет их в потоке GUI. Это несколько более обременительно, но и безопаснее, потому что ваши классы GUI могут быть написаны, не заботясь о безопасности потоков.