проблемы проектирования в проблеме производитель-потребитель


Я работаю над небольшим многопоточным проектом.Система может быть разделена на 2 части, А и В. потоки данных от А к Б.
Часть продолжает получать необработанные данные из внешнего мира, выполнять некоторые преобразования и затем генерировать тысячи новых данных, назовем это A_OUTPUT.
B часть сделать некоторые вычисления на основе каждого A_OUTPUT, а затем генерировать еще больше данных, может быть в десять раз больше a_output.

Я запутался в том, как синхронизировать 2 части.
Моя собственность дизайн заключается в создании рабочей очереди, а также блокировки, защищающей очередь между двумя подразделениями. Также создайте событие, чтобы указать, пуста ли рабочая очередь или нет.

Часть состоит из нескольких потоков, каждый поток получает данные извне и генерирует A_OUTPUT, каждый раз, когда один поток генерирует A_OUTPUT, поток получает блокировку очереди, толкает A_OUTPUT в очередь, освобождает блокировку, а затем запускает событие.

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

Проблема этой конструкции очевидна, поток супервизора B будет мчаться с несколькими потоками A, чтобы выиграть блокировку очереди. может быть, когда б, наконец, владел замком, в нем уже было десять или больше А_outputs. очередь, и самый старый A_OUTPUT был сгенерирован давным-давно. Я хочу, чтобы каждый A_OUTPUT обрабатывался как можно быстрее.

Я знаю, что могу разделить рабочую очередь на несколько меньших очередей или добавить больше потоков супервизора B в битву блокировок, чтобы сократить среднее время ожидания каждого A_OUTPUT, прежде чем он будет обработан. но может ли существовать более подходящий дизайн?

И еще один вопрос, существует ли какая-либо парадигма или шаблон проектирования для различных целей многопоточности программы?

4 2

4 ответа:

Это довольно классическая задача, которая хорошо описана в Википедии

Я могу рекомендовать следующий подход:

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

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

Consumer проверяет, есть ли в очереди какие-либо данные, потребляет их и уведомляет о состоянии "не полный"

Также вы можете использовать очередь без блокировки для повышения производительности. Проверьте TBB или недавно объявленный Boost.Lockless (в настоящее время рассматривается). Кстати, с помощью ТББ вся задача значительно упрощается, просто используют их диспетчера и контейнеры и забыли про них. явная синхронизация

Как и @thiton, я не вижу необходимости в потоке супервайзера - это кажется ненужным усложнением?

Это, по-видимому, вопрос управления потоком, а также проектирования очередей. Учитывая, что вам, по-видимому, нужно, я бы выбрал две очереди производитель-потребитель и ограниченное число экземпляров A_OUTPUT. Я бы создал 1000 (скажем) экземпляров A_OUTPUT и поместил их в одну очередь P-C, формируя потокобезопасный пул объектов при запуске (очередь пула). Запуск потоков A, pop A_OUTPUT из пула и начать "извлечение необработанных данных из внешнего мира". Когда поток A получает данные в свой A_OUTPUT, он помещает их в другую очередь P-C (очередь связи). Пул потоков B ожидает в очереди связи. Когда A_OUTPUT становится доступным в очереди связи, поток B получит его и обработает. Когда поток B закончил работу с данными, is возвращает A_OUTPUT обратно в очередь пула. Таким образом, экземпляры A_OUTPUT циркулируют, перенося данные с одного конца системы на другой. другие, а затем вернуться к началу через очередь пула.

Подобные конструкции позволяют управлять потоком через несколько потоков / пулов. Существует достаточная "слабина" в очередях, чтобы позволить всплески высокой нагрузки, но беглые потоки / объекты невозможны - если через них проходит слишком много данных, потоки A найдут пул пустым и заблокируют его до тех пор, пока экземпляры A_OUTPUT не станут доступными - когда они есть, потоки A продолжат получать больше данных.

Такая система может быть настроена на время выполнения. Добавление / удаление потоков из пулов потоков A/B и увеличение / уменьшение глубины пула объектов легко.

Oh-an вам не нужны сложные ограниченные очереди P-C. Если каждая очередь может содержать количество объектов в пуле, этого достаточно - больше никогда не будет доступно.

Rgds, Мартин

Проблему возраста очереди можно решить, ограничив ее размер, как предложил Энди т.

Кроме того, если вы отбросите поток супервизора, ваш макет станет намного проще. Пусть потоки A получают мьютекс, когда они заканчивают обработку, помещают свой элемент в очередь и ждут, пока очередь не опустеет достаточно, чтобы продолжить. Каждый поток-потребитель ждет, пока элемент в очереди станет доступным, получает его и работает с ним.

Так как существует достаточно близкое число нити а и Б, голодание должно быть маловероятным. Кроме того, вы должны использовать такие методы, как pthread_cond_signal (вместо pthread_cond_broadcast), которые пробуждают только один поток с каждой стороны, минимизируя количество гонок.

Здесь есть статья, которая может помочь: http://drdobbs.com/showArticle.jhtml?articleID=184401751

(ой, только что заметил, что это старая тема, это был просто ответ, отредактированный час назад.)