Шаблоны / принципы для потокобезопасных очередей и программы "master / worker" в Java
У меня есть проблема, которая, я считаю, является классическим шаблоном мастер/работник, и я ищу совет по реализации. Вот что я сейчас думаю о проблеме:
есть какая-то глобальная "очередь", и это центральное место, где хранится "работа, которую нужно сделать". Предположительно, эта очередь будет управляться своего рода" главным " объектом. Нити будут порождены, чтобы найти работу, и когда они найдут работу, они скажут мастеру (что бы это ни было), чтобы "добавить в очередь работы".
мастер, возможно, на интервале, породит другие потоки, которые фактически выполняют выполняемую работу. Как только поток завершит свою работу, я бы хотел, чтобы он уведомил мастера о завершении работы. Затем мастер может удалить эту работу из очереди.
в прошлом я делал значительное количество программирования потоков на Java, но все это было до JDK 1.5, и, следовательно, я не знаком с соответствующими новыми API за ведение этого дела. Я понимаю, что JDK7 будет иметь fork-join, и это может быть решением для меня, но я не могу использовать продукт раннего доступа в этом проекте.
проблемы, как я их вижу, таковы:
1) как "потоки, выполняющие работу", связываются с мастером, сообщая им, что их работа завершена и что мастер теперь может удалить работу из очереди
2) Как эффективно иметь мастерскую гарантию что работа только один раз по расписанию. Например, предположим, что в этой очереди миллион элементов, и она хочет сказать работнику: "иди, сделай эти 100 вещей". Каков наиболее эффективный способ гарантировать, что, когда он планирует работу следующему работнику, он получает "следующие 100 вещей", а не "100 вещей, которые я уже запланировал"?
3) выбор соответствующей структуры данных для очереди. Я думаю, что "потоки, которые находят работу", потенциально могут найти ту же работу, чтобы сделать больше, чем один раз, и они отправят сообщение мастеру, говоря: "вот работа", и мастер поймет, что работа уже запланирована и, следовательно, должна игнорировать сообщение. Я хочу убедиться, что я выбираю правильную структуру данных, чтобы это вычисление было как можно дешевле.
Традиционно я бы сделал это в базе данных, в некотором роде конечным образом, работая с "задачами" от начала до конца. Однако в этой проблеме я не хочу использовать a базы данных из-за большого объема и волатильности очереди. Кроме того, я хотел бы сохранить это как можно более легким. Я не хочу использовать любой сервер приложений, если этого можно избежать.
вполне вероятно, что эта проблема, которую я описываю, является общей проблемой с известным именем и принятым набором решений, но я, с моей низкой степенью не-CS, не знаю, как это называется (т. е., пожалуйста, будьте осторожны).
Спасибо за любые указатели.
6 ответов
насколько я понимаю ваши требования, вам нужно ExecutorService. ExecutorService есть
submit(Callable task)
метод, который возвращает значение будущее. Будущее-это блокирующий способ общения от работника к мастеру. Вы можете легко развернуть этот механизм для работы асинхронным способом. И да, ExecutorService также поддерживает Рабочую очередь, такую как ThreadPoolExecutor. Поэтому в большинстве случаев вам не нужно беспокоиться о планировании. Ява.утиль.параллельный пакет уже имеет эффективные реализации потокобезопасной очереди (ConcurrentLinked queue - nonblocking и LinkedBlockedQueue - blocking).
проверить java.утиль.параллельный в библиотеке Java.
в зависимости от вашего приложения это может быть так же просто, как сборка некоторой очереди блокировки и ThreadPoolExecutor.
кроме того, книга параллелизм Java на практике Брайан Гетц может быть полезным.
во-первых, почему вы хотите держать элементы после того, как работник начал их делать? Обычно у вас будет очередь работы, и работник берет элементы из этой очереди. Это также решит проблему"как я могу предотвратить получение работниками одного и того же элемента".
на ваши вопросы:
1) Как иметь " потоки, выполняющие работа " общение с мастером говорят, что их работа завершите и что мастер может теперь удалить работу из очередь
мастер мог слушать рабочих, используя слушатель/наблюдатель шаблон
2) Как эффективно иметь мастера гарантируем, что работа только когда-либо один раз по расписанию. Например, скажем в этой очереди миллион элементов, и хочет сказать работнику: "иди и сделай это". 100 вещей". Что наиболее эффективно способ гарантировать это, когда это графики работы к следующему работнику, it получает "следующие 100 вещей" и не "100 вещей, которые я уже запланированные"?
см. выше. Я позволял рабочим вытаскивать вещи из очереди.
3) выбор соответствующих данных структура очереди. Мое мышление вот что " потоки поиска работы to do" потенциально может найти то же самое поработать не один раз, и они отправить сообщение учителя: "вот работа", и мастер понимаю, что работа уже была предусмотренный графиком и, следовательно, должны проигнорируйте сообщение. Я хочу, чтобы что я выбираю правильную структуру данных такие, что это вычисление так же дешево по возможности.
есть варианты блокирование очереди начиная с Java 5
Не забудьте Джини и Javaspaces. То, что вы описываете, очень похоже на классический шаблон производителя/потребителя, в котором преуспевают космические архитектуры.
производитель будет записывать задания в пространство. 1 или более потребителей будут выполнять задания (по транзакции) и работать над этим параллельно, а затем записывать результаты. Поскольку он находится под транзакцией, если возникает проблема, задание снова становится доступным для другого потребителя .
вы можете увеличить масштаб тривиально, добавляя больше потребителей. Это особенно хорошо работает, когда потребители являются отдельными виртуальными машинами и масштабируются по сети.
Если вы открыты к идее весны, то проверите вне их проект интеграции весны. Это дает вам все очереди / поток-пул boilerplate из коробки и оставляет вас сосредоточиться на бизнес-логике. Конфигурация сведена к минимуму с помощью @annotations.
кстати, Goetz очень хорош.
Это не похоже на проблему master-worker, но специализированный клиент выше threadpool. Учитывая, что у вас есть много потоков очистки и не много процессоров, может быть, стоит просто сделать проход очистки, а затем вычислительный проход. Сохраняя рабочие элементы в наборе, ограничение уникальности удаляет дубликаты. Второй проход может отправить всю работу в ExecutorService для выполнения процесса параллельно.
модель мастер-работника обычно предполагается, что поставщик данных имеет всю работу и предоставляет ее мастеру для управления. Мастер контролирует выполнение работ и имеет дело с распределенными вычислениями, тайм-аутами, сбоями, повторными попытками и т. д. Абстракция fork-join является рекурсивным, а не итеративным поставщиком данных. Абстракция map-reduce-это многоступенчатый мастер-работник, который полезен в определенных сценариях.
хорошим примером master-worker является для тривиально параллельных задач, таких как поиск простых числа. Другой-загрузка данных, где каждая запись независима (проверка, преобразование, этап). Необходимость в обработке известного рабочий набор, обработки ошибок и т. д. это то, что делает модель master-worker отличной от пула потоков. Вот почему мастер должен контролировать и выталкивать рабочие единицы, тогда как threadpool позволяет рабочим вытаскивать работу из общей очереди.