Многопоточность работы, выполненной в цикле for с использованием пула потоков

1

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

double[] largeArray = getMyLargeArray();
double result = 0;
for (double d : largeArray)
    result += d;
System.out.println(result);

В этом примере я мог бы распределить работу, выполненную внутри цикла for, в течение нескольких потоков и убедиться, что все потоки завершены, прежде чем приступать к печати result. Поэтому я придумал что-то похожее на это:

final double[] largeArray = getMyLargeArray();
int nThreads = 5;
final double[] intermediateResults = new double[nThreads];

Thread[] threads = new Thread[nThreads];
final int nItemsPerThread = largeArray.length/nThreads;
for (int t = 0; t<nThreads; t++) {
    final int t2 = t;
    threads[t] = new Thread(){
        @Override public void run() {
            for (int d = t2*nItemsPerThread; d<(t2+1)*nItemsPerThread; d++)
                intermediateResults[t2] += largeArray[d];
        }
    };
}
for (Thread t : threads)
    t.start();
for (Thread t : threads)
    try {
        t.join();
    } catch (InterruptedException e) { }
double result = 0;
for (double d : intermediateResults)
    result += d;
System.out.println(result);

Предположим, что длина largeArray массива делится на nThreads. Это решение работает правильно.

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

Поскольку я сохраняю свои промежуточные результаты в массиве размера, который должен быть известен заранее, я думал об использовании пула потоков фиксированного размера. Однако у меня возникли проблемы с тем, чтобы сообщить нить о том, в каком месте в массиве должен храниться его результат. Должен ли я определить свой собственный ThreadFactory?

Или мне лучше использовать массив ExecutorService созданный методом Executors.newSingleThreadExecutor(ThreadFactory myNumberedThreadFactory)?

Обратите внимание, что в моей реальной программе очень сложно заменить double[] intermediateResults на что-то другого типа. Я бы предпочел решение, которое ограничивается созданием правильного типа пула потоков.

Теги:
multithreading
for-loop
executorservice
threadpoolexecutor

3 ответа

1
Лучший ответ

Однако у меня возникли проблемы с тем, чтобы сообщить thread о том, в каком месте в array должен храниться его результат. Должен ли я определить свой собственный ThreadFactory?

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

ThreadPoolExecutor - действительно хорошее решение. Также посмотрите на FutureTask если у вас есть runnables с результатами.

  • 0
    Этот простой ответ на самом деле правильный. Я думал, что Runnable должен знать, в каком потоке он находится, а он, в свою очередь, знает, в каком месте массива он должен храниться. Таким образом, я могу сказать, что поток 1 хранится на месте 1 массива. На самом деле это мышление просто странно, поскольку потоки параллельны, и нет интуитивно понятного способа, которым поток 1 по праву помечается как «1». Я могу просто указать 5 (или вообще: количество ядер, которые у меня есть). Runnable s хранит каждое в уникальном месте в массиве.
  • 0
    Согласитесь ли вы с тем, что использование Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) - хорошая вещь?
Показать ещё 3 комментария
1

ExecutorService предоставляет вам API для получения результата из пула потоков через интерфейс Future:

 Future<Double> futureResult = executorService.submit(new Callable<Double>() {
     Double call() {
         double totalForChunk = 0.0;
         // do calculation here
         return totalForChunk;
     }
 });

Теперь все, что вам нужно сделать, это отправить задания (экземпляры Callable) и дождаться, когда результат будет доступен:

 List<Future<Double>> results = new ArrayList<Double>();
 for (int i = 0; i < nChunks; i++) {
     results.add(executorService.submit(callableTask));
 }

Или даже проще:

 List<Future<Double>> results = executorService.invokeAll(callableTaskList);

Остальное легко, перебирать results и собирать всего:

 double total = 0.0;
 for (Future<Double> result : results) {
     total += result.get(); // this will block until your task is completed by executor service
 }

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

  • 0
    На самом деле это хорошее решение для примера, но, поскольку я (возможно, неясно) объяснил, я не могу изменить тип результатов на список. Проблема является более фундаментальной в моей реальной программе: вместо одного двойного результата потоки хранят свои результаты в больших структурах данных двойных чисел. ExecutorService было бы очень неудобно возвращать все эти структуры данных и размещать их в нужных местах.
  • 0
    Вы всегда можете передать целевые структуры данных в вызываемые экземпляры и поместить результаты прямо туда. Не требуется ничего возвращать с работы. Но вы все равно можете использовать Feature.get () для синхронизации при завершении задания.
0

Вам было бы лучше создавать "рабочие" потоки, которые берут информацию о работе, которая будет выполняться из очереди. Тогда ваш процесс должен был создать первоначально пустой WorkQueue, а затем создать и запустить рабочие потоки. Каждый рабочий поток забирал свою работу из очереди, выполнял работу и помещал работу в "завершенную" очередь для мастера, чтобы забрать и обработать.

  • 0
    То, что вы описываете, звучит как SingleThreadExecutor. (См. Второй-последний абзац моего поста)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню