Шаблоны/принципы для потокобезопасных очередей и программы "мастер/работник" в Java

У меня есть проблема, которую я считаю классическим шаблоном мастера/рабочего, и я ищу совета по реализации. Вот что я сейчас думаю о проблеме:

Там есть глобальная "очередь", и это центральное место, где сохраняется "работа, которая будет выполнена". Предположительно, эта очередь будет управляться каким-то "главным" объектом. Темы будут созданы, чтобы найти работу, и когда они найдут работу, они расскажут мастеру (что бы это ни было) "добавить это в очередь выполняемой работы".

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

В прошлом я довольно много программировал потоков на Java, но все это было до JDK 1.5, и поэтому я не знаком с соответствующими новыми API для обработки этого случая. Я понимаю, что JDK7 будет иметь fork-join, и это может быть для меня решением, но я не могу использовать продукт раннего доступа в этом проекте.

Проблемы, как я их вижу, следующие:

1), как "потоки, выполняющие работу", сообщаются ведущему, сообщая им, что их работа завершена, и что мастер теперь может удалить работу из очереди

2), как эффективно иметь гарантию мастера, что работа выполняется только один раз. Например, предположим, что эта очередь имеет миллион элементов, и она хочет сказать работнику "пойти на эти 100 вещей". Какой самый эффективный способ гарантировать, что когда он планирует работать на следующего работника, он получает "следующие 100 вещей", а не "100 вещей, которые я уже запланировал"?

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

Традиционно я бы сделал это в базе данных, в виде машины с конечным состоянием, работая "задачи" от начала до конца. Однако в этой проблеме я не хочу использовать базу данных из-за большого объема и волатильности очереди. Кроме того, я хотел бы сохранить это как можно более легким. Я не хочу использовать сервер приложений, если этого можно избежать.

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

Спасибо за любые и все указатели.

Ответ 1

Насколько я понимаю ваши требования, вам нужно ExecutorService. ExecutorService имеют

submit(Callable task)

возвращающее значение Future. Будущее - это блокирующий способ общения от рабочего к хозяину. Вы можете легко расширить этот механизм, чтобы работать асинхронно. И да, ExecutorService также поддерживает рабочую очередь, такую ​​как ThreadPoolExecutor. Поэтому в большинстве случаев вам не нужно беспокоиться о планировании. Пакет java.util.concurrent уже имеет эффективные реализации потоковой безопасной очереди (ConcurrentLinked queue - неблокирование и блокировка LinkedBlockedQueue).

Ответ 2

Откроется java.util.concurrent в библиотеке Java.

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

Кроме того, может оказаться полезной книга Java Concurrency на практике Брайана Гетца.

Ответ 3

Во-первых, почему вы хотите хранить элементы после того, как работник начал их делать? Как правило, у вас будет очередь работы, и рабочий берет элементы из этой очереди. Это также решило бы "как я могу помешать работникам получить тот же товар" -проблема.

На ваши вопросы:

1), как иметь "потоки, выполняющие работа" вернется к мастеру говоря им, что их работа полный и что мастер может сейчас удалить работу из очереди

Мастер мог прослушивать рабочих, используя шаблон слушателя/наблюдателя

2), как эффективно использовать мастер гарантируют, что работа будет только когда-либо запланировано один раз. Например, скажем эта очередь имеет миллион элементов, и это хочет сказать работнику "пойти делать эти 100 вещей". способ гарантировать, что когда график работы для следующего работника, он получает "следующие 100 вещей", а не "100 вещей, которые у меня уже есть запланировано"?

См. выше. Я бы позволил рабочим вытащить предметы из очереди.

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

Существуют реализации блокировки очереди, поскольку Java 5

Ответ 4

Не забывайте Jini и Javaspaces. То, что вы описываете, очень похоже на классический образец производителя/потребителя, который превосходит архитектуры на основе пространства.

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

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

Ответ 5

Если вы открыты для идеи Spring, ознакомьтесь с их проектом интеграции Spring. Он дает вам полный набор сценариев очереди/потокового пула и позволяет сосредоточиться на бизнес-логике. Конфигурация сведена к минимуму с помощью @annotations.

btw, Goetz очень хорош.

Ответ 6

Это не похоже на проблему мастера-работника, а на специализированный клиент выше потока. Учитывая, что у вас много потоков очистки, а не много процессоров, может оказаться целесообразным просто сделать пропущенный пас, а затем вычислительный проход. Сохраняя рабочие элементы в наборе, ограничение уникальности удалит дубликаты. Второй проход может передать всю работу в ExecutorService для параллельного выполнения процесса.

Модель мастер-работника обычно предполагает, что поставщик данных имеет всю работу и передает ее мастеру для управления. Мастер контролирует выполнение работы и имеет дело с распределенными вычислениями, тайм-аутами, сбоями, попытками и т.д. Абстракция fork-join является рекурсивным, а не итеративным поставщиком данных. Абстракция с уменьшением масштаба - это многоступенчатый мастер-мастер, который полезен в определенных сценариях.

Хорошим примером мастера является тривиально параллельные проблемы, такие как поиск простых чисел. Другая - это загрузка данных, где каждая запись является независимой (проверка, преобразование, этап). Необходимость обработки известного рабочего набора, сбоев обработки и т.д. Делает модель мастера-работника отличной от пула потоков. Вот почему мастер должен контролировать и выталкивает рабочие единицы, тогда как threadpool позволяет работникам вытаскивать работу из общей очереди.