Сигналы и темы - хорошее или плохое дизайнерское решение? - PullRequest
5 голосов
/ 11 июня 2010

Мне нужно написать программу, которая выполняет высокопроизводительные вычисления. Программа может работать несколько дней. Расчет может быть легко разделен на разные темы без необходимости обмена данными. Я хочу графический интерфейс или веб-сервис, который информирует меня о текущем состоянии.

Мой текущий дизайн использует BOOST :: signal2 и BOOST :: thread. Компилируется и пока работает как положено. Если поток завершил одну итерацию и появились новые данные, он вызывает сигнал, который подключен к слоту в классе GUI.

Мой вопрос (ы):

  • Является ли эта комбинация сигналов и потоков мудрой идеей? На другом форуме кто-то советовал кому-то другому не «идти по этому пути».
  • Есть ли поблизости потенциальные подводные камни, которые мне не удалось увидеть?
  • Реально ли мое ожидание, что будет проще использовать мой класс GUI для предоставления веб-интерфейса или QT, VTK или любого другого окна?
  • Есть ли более умная альтернатива (как и другие буст-либы), которую я пропустил?

следующий код компилируется с

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;
}

Ответы [ 2 ]

12 голосов
/ 11 июня 2010

Является ли эта комбинация сигналов и потоков мудрой идеей?На другом форуме кто-то советовал кому-то другому не «идти по этому пути».

Вроде бы звук.Можете ли вы предоставить ссылку на другой поток?Они объясняли свои аргументы?

Есть ли поблизости потенциальные подводные камни, которые я не смог увидеть?

Если они такие, я тоже их не вижу.Вам нужно позаботиться о том, чтобы уведомления были поточно-ориентированными (запуск сигнала не переключает контексты потока, на ваш GuiClass::slot_data_changed следует вызывать из всех других потоков.

Реалистичны ли мои ожидания, что будет проще использовать класс GUI для предоставления веб-интерфейса или QT, VTK или любого другого окна?

Это будет непросто. Исправитьэто, вам нужно было бы сделать так, чтобы ваше уведомление переключало контексты потоков. Вот что я бы сделал:

Пусть ваш GuiClass будет абстрактным базовым классом, реализующим свою собственную очередь сообщений. Когда GuiClass::slot_data_changed вызываетсяваши потоки, вы блокируете мьютекс и публикуете копию полученного уведомления во внутренней (private:) очереди сообщений. В потоке GuiClass вы создаете функцию, которая блокирует мьютекс и ищет уведомления в очереди.Эта функция должна выполняться в потоке клиентского кода (в потоке конкретных классов, которые вы специализируете на аннотации GuiClass).

Преимущества:

  • ваш базовый класс инкапсулирует и изолирует переключение контекста потока, прозрачно для его специализаций.

Недостатки:

  • ваш код клиента должен либозапустите метод опроса или разрешите его запуск (как функцию обработки потока).

  • это немного сложно:)

Являются ли мои ожидания реалистичными, что будет легче использовать мой класс GUI для предоставления веб-интерфейса или QT, VTK или любого другого окна?

Не видит, но этоне просто.Помимо переключения контекста потока, могут быть и другие проблемы, которые я пропускаю в данный момент.

Есть ли более умная альтернатива (как и другие библиотеки повышения), которую я пропустил?

Не другие библиотеки повышения, но то, как вы пишете свои потоки, нехорошо: объединения выполняются последовательно в вашем коде.Чтобы иметь только один 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() будет вызвано в контексте tB (в стеке потока B).

К чДля этого (чтобы запустить a.something() в контексте tA), вам нужно переключить контекст потока .Это означает, что вместо b, говорящего a "run A::something()", b сообщает a "run A ::thing ()` в вашем собственном контексте потока ".

Классическая реализацияшаги:

  • b отправляет сообщение a изнутри tB

  • a опрашивает сообщения изнутри tA

  • Когда a находит сообщение от b, он запускает a.something () в пределах tA.

Это переключение контекстов потоков (выполнение A::something будет выполняться в tA вместо tB, как это было бы, если бы вызывалось непосредственно из b).

По ссылке, которую выпри условии, что, кажется, это уже реализовано boost::asio::io_service, поэтому, если вы используете это, вам не нужно реализовывать это самостоятельно.

7 голосов
/ 11 июня 2010

Есть одна очень важная ловушка:

Насколько я понимаю, безопасность потока сигналов2 слоты выполняются в потоке сигнализации . Большинство библиотек GUI (особенно Qt и OpenGL) должны делать все рисование из одного потока. В общем, это не проблема, но нужно немного позаботиться. У вас есть два варианта:

Во-первых, вы должны быть осторожны, чтобы не рисовать внутри GuiClass::slot_data_changed (поскольку вы используете Qt, взгляните на QCoreApplication :: postEvent (извините, не разрешено публиковать ссылки на документы Qt)).

Во-вторых, вы сами создаете очередь сообщений, которая сохраняет вызовы слотов и выполняет их в потоке графического интерфейса. Это несколько более обременительно, но и более безопасно, потому что ваши классы GUI могут быть написаны без заботы о безопасности потоков.

...