Сигналы и потоки - хорошее или плохое решение для дизайна?

StackOverflow https://stackoverflow.com/questions/3021854

Вопрос

Я должен написать программу, которая выполняет высокомешительно интенсивные расчеты. Программа может работать в течение нескольких дней. Расчет может быть легко отделен в разных потоках без необходимости общих данных. Я хочу 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 могут быть написаны, не заботясь о безопасности потоков.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top