Методы статического класса потока против глобальной области видимости - PullRequest
1 голос
/ 17 февраля 2012

Представьте себе функциональность приложения, которое требует до 5 потоков, обрабатывающих данные, эти потоки используют буферы, мьютекс и события для взаимодействия друг с другом. Производительность критична, а язык - C ++.

Функциональность может быть реализована как одна единица (компиляция) с одним классом, и только один экземпляр этого класса может быть создан для приложения. Сам класс реализует 1 из потоков в методе run () , который порождает другие 4 потока, управляет ими и собирает их, когда пользователь закрывает приложение.

В чем преимущество выбора одного из следующих методов перед другим (пожалуйста, дайте мне знать о каком-либо лучшем подходе)?

  1. Добавьте 5 статических методов к классу, каждый из которых выполняет один поток, мьютекс и другие данные, совместно используемые в качестве статических переменных класса.
  2. Добавить 5 глобальных функций (без области видимости) и использовать глобальные переменные, события и мьютекс (как если бы это было C)
  3. полностью изменить шаблон, добавить еще 4 класса, каждый из которых реализует один из потоков, и обмениваться данными через глобальные переменные.

Вот некоторые мысли и проблемы, которые следует рассмотреть (пожалуйста, исправьте их, если они не правы):

  1. Имея потоки в качестве членов класса (конечно, статические), они могут полагаться на одноэлементный доступ для доступа к нестатическим функциям-членам, это также дает им пространство имен, что само по себе кажется хорошей идеей.
  2. Используя статические методы класса, скоро файл заголовка класса будет содержать много статических переменных (и других вспомогательных статических методов). Необходимость объявления переменных в файле заголовка класса может принести дополнительные зависимости другим модулям, которые включают файл заголовка. Если переменные объявлены глобально, они могут быть скрыты в отдельном заголовочном файле.
  3. Статические переменные класса должны быть определены где-то в коде, так что это удваивает набираемый материал объявления.
  4. Компиляторы могут воспользоваться преимуществами разрешения пространства имен для более оптимизированного кода (в отличие от глобальных переменных, возможно, в разных единицах).
  5. Один модуль потенциально может быть лучше оптимизирован, тогда как оптимизация всей программы медленная и, вероятно, менее плодотворная.
  6. Если модуль увеличивается, мне нужно переместить некоторую часть кода в отдельный модуль, поэтому у меня будет один класс с несколькими (компиляционными) модулями, это анти-шаблон или нет?
  7. Если используется более одного класса, каждый из которых обрабатывает один поток, опять же можно задать один и тот же вопрос для выбора между статическими методами и глобальными функциями для реализации потоков. Кроме того, для этого требуется больше кода, не реальная проблема, но стоит ли это дополнительных затрат?

Пожалуйста, ответьте на это, предполагая, что нет библиотеки, такой как Qt, и затем предполагая, что мы можем положиться на QThread и реализовать один поток для метода run () .

Edit1: количество нитей фиксировано для дизайна, число 5 - только пример. Пожалуйста, поделитесь своими мыслями о подходах / шаблонах, а не о деталях.

Edit2: я нашел этот ответ (на другой вопрос) очень полезным, я думаю, что первый подход использует классы как пространства имен. Второй подход можно смягчить, если он связан с пространством имен.

1 Ответ

3 голосов
/ 18 февраля 2012

Источники

Во-первых, вы должны прочитать все статьи о параллелизме Херба Саттера:

http://herbsutter.com/2010/09/24/effective-concurrency-know-when-to-use-an-active-object-instead-of-a-mutex/

Это ссылка на пост последней статьи, который содержитссылки на все предыдущие статьи.

Каков ваш случай?

Согласно следующей статье: Сколько масштабируемости у вас есть или нужно? (http://drdobbs.com/parallel/201202924), вы в деле O(K): Fixed.То есть у вас есть фиксированный набор задач, которые должны выполняться одновременно.

По описанию вашего приложения у вас есть 5 потоков, каждый из которых выполняет совершенно разные задачи, поэтому вы должны иметь 5 потоков, возможно,надеясь, что один или несколько из них все еще могут разделить свои задачи на несколько потоков (и, следовательно, используя пул потоков), но это было бы бонусом.

Я позволю вам прочитать статью для получения дополнительной информации.

Вопросы по дизайну

О синглтоне

Забудьте о синглтоне.Это тупой, чрезмерно используемый шаблон .

Если вы действительно действительно хотите ограничить количество экземпляров вашего класса (и серьезно, разве у вас нет ничего лучше, чем это?), Вы должны разделить дизайн на две части: один класс для данных и один класс, чтобы обернуть предыдущий класс в одноэлементное ограничение.

О единицах компиляции

Сделайте ваши заголовки и источники легко доступными.читать.Если вам нужно иметь реализацию класса в нескольких источниках, пусть будет так.Я называю источник соответственно.Например, для класса MyClass у меня будет:

  • MyClass.hpp: заголовок
  • MyClass.cpp: основной источник (с конструкторами и т. Д.)
  • MyClass.Something.cpp: обработка исходного кода с чем-либо
  • MyClass.SomethingElse.cpp: обработка исходного кода с чем-то другим
  • и т. Д.

Об оптимизации компилятора

Последние компиляторы могут встроить код из разных модулей компиляции (я видел эту опцию в Visual C ++ 2008, IIRC).Я не знаю, работает ли целая глобальная оптимизация хуже, чем компиляция «из одного модуля», но даже если это так, вы все равно можете разделить свой код на несколько источников, а затем включить в один глобальный источник все.Например:

  • MyClassA.header.hpp
  • MyClassB.header.hpp
  • MyClassA.source.hpp
  • MyClassB.source.hpp
  • global.cpp

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

Ваш случай, но лучше?

Ваш вопрос и комментарии говорято монолитном дизайне больше, чем о производительности или потоке, поэтому я могу ошибаться, но что вам нужно, так это простой рефакторинг.

Я бы использовал 3-й метод (один класс на поток), потому что с классами приходит private / publicдоступ, и, следовательно, вы можете использовать это для защиты данных, принадлежащих одному потоку, только сделав его закрытым.

Следующие рекомендации могут помочь вам:

1 - каждый поток долженбыть скрытым в одном нестатическом объекте

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

Обычно функции построения потоков позволяют передавать поИнтер к функции с параметром контекста void *, поэтому используйте его для передачи указателя this на функцию основного потока:

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

Вот некоторый код:

// Some fictious thread API
typedef void (*MainThreadFunction)(void * p_context) ;
ThreadHandle CreateSomeThread(MainThreadFunction p_function, void * p_context) ;

// class header
class MyClass
{
   public :
      MyClass() ;
      // etc.

      void         run() ;

   private :
      ThreadHandle m_handle ;

      static void  threadMainStatic(void * p_context) ;
      void         threadMain() ;
}

.

// source
void MyClass::run()
{
   this->m_handle = CreateSomeThread(&MyClass::threadMainStatic, this) ;
}

void MyClass::threadMainStatic(void * p_context)
{
   static_cast<MyClass *>(p_context)->threadMain() ;
}

void MyClass::threadMain()
{
   // Do the work
}

Отказ от ответственности: это не было проверено в компиляторе.Воспринимайте это как псевдо-код C ++ больше, чем реальный код.YMMV.

2 - определение данных, которые не являются общими.

Эти данные могут быть скрыты в закрытом разделе объекта-владельца, и если они защищены синхронизацией, то эта защита является избыточной (так как данные НЕ передаются)

3 - определение данных для общего доступа

... и проверить его синхронизацию (блокировки, атомарный доступ)

4 - Каждый класс должен иметь свой собственный заголовок и источник

... и при необходимости защитить доступ к своим (общим) данным с синхронизацией

5 - максимально защитить доступ

Если одна функция используется классом и только классом и на самом деле не нуждается в доступе к внутренним объектам класса, то она может быть скрыта в анонимном пространстве имен.

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

и т.д.

...