Особенности проектирования адаптивного пула потоков в Java - PullRequest
4 голосов
/ 19 июля 2009

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

Практически я хочу добиться того же поведения, что и новая реализация пула потоков в C # 4.0

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

Версия C # выполняет самоинструменты для достижения оптимального использования. Какие инструменты самообучения доступны в Java и какое влияние это оказывает на производительность?

Возможен ли совместный подход, когда задача сигнализирует о своем намерении (например, ввод интенсивной операции ввода-вывода, переход на фазу интенсивной работы ЦП)?

Любые предложения приветствуются.

Редактировать Основано на комментариях:

Целевые сценарии могут быть:

  • Локальный просмотр и обработка файлов
  • веб-сканирование
  • Мультисервисный доступ и агрегация

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

Например, у меня есть 100 веб-сервисов для доступа подряд. Если я создаю 100 CTP, он запустит 100 потоков для выполнения операции, и тонна множества запросов ввода-вывода и передачи данных обязательно наткнется на ноги друг друга. В случае статического теста я смог бы поэкспериментировать и определить оптимальный размер пула, но я хочу, чтобы он был адаптивно определен и применен таким образом.

Ответы [ 4 ]

2 голосов
/ 28 июля 2009

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

Каждый поток, представленный в пул, отправит ресурс, который является его узким местом, то есть «ЦП», «Сеть», «C: \» и т. Д.

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

1 голос
/ 19 июля 2009

В приведенном примере

Result[] a = new Result[N];
for(int i=0;i<N;i++) {
    a[i] = compute(i);
}

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

// defined earlier
int procs = Runtime.getRuntime().availableProcessors();
ExecutorService service = Executors.newFixedThreadPool(proc);

// main loop.
Future<Result>[] f = new Future<Result>[N];
for(int i = 0; i < N; i++) {
    final int i2 = i;
    a[i] = service.submit(new Callable<Result>() {
        public Result call() {
            return compute(i2);
        }
    }
}
Result[] a = new Result[N];
for(int i = 0; i < N; i++) 
    a[i] = f[i].get();

Это не сильно изменилось за последние 5 лет, так что это не так круто, как это было, когда это было впервые доступно. Что на самом деле не хватает Java, так это замыкания. Вместо этого вы можете использовать Groovy, если это действительно проблема.

Дополнительно: если вы заботитесь о производительности, а не в качестве примера, вы будете вычислять Фибоначчи параллельно, потому что это хороший пример функции, которая работает быстрее, если вы вычисляете ее как однопоточный.

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

0 голосов
/ 30 июля 2009

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

Вместо этого я бы посоветовал взглянуть на архитектуру платформы Apache MINA для вдохновения. (http://mina.apache.org/) Это высокопроизводительная веб-инфраструктура - они описывают ее как серверную инфраструктуру, но я думаю, что их архитектура хорошо работает и для обратных сценариев, таких как spidering и многосерверные клиенты. (На самом деле, вы могли бы даже быть в состоянии использовать его из коробки для вашего проекта.)

Они используют библиотеки Java NIO (неблокирующий ввод / вывод) для всех операций ввода-вывода и делят работу на два пула потоков: небольшой и быстрый набор потоков сокетов, а также больший и медленный набор бизнес-логики. потоки. Итак, слои выглядят следующим образом:

  • На конце сети большой набор каналов NIO, каждый с буфером сообщений
  • Небольшой пул потоков сокетов, которые проходят через циклический список каналов. Их единственная задача - проверить сокет и переместить любые данные в буфер сообщений - и, если сообщение готово, закройте его и перенесите в очередь заданий. Эти парни быстры, потому что они просто перемещают биты и пропускают любые сокеты, заблокированные на IO.
  • Одна очередь заданий, которая сериализует все сообщения
  • Большой пул потоков обработки, которые извлекают сообщения из очереди, анализируют их и выполняют любую необходимую обработку.

Это обеспечивает очень хорошую производительность - ввод-вывод разделен на собственный уровень, и вы можете настроить пул потоков сокетов для максимизации пропускной способности ввода-вывода, а также отдельно настроить пул потоков обработки для управления использованием ЦП / ресурсов.

0 голосов
/ 19 июля 2009

Я думаю, вы должны следить за загрузкой процессора, в зависимости от платформы. Узнайте, сколько процессоров / ядер у вас есть, и следите за нагрузкой. Когда вы обнаружите, что нагрузка мала, и у вас все еще есть работа, создайте новые потоки - но не более, чем в x раз num-cpus (скажем, x = 2).

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

...