Должен ли я использовать ThreadPools или параллельную библиотеку задач для операций с привязкой к IO

В одном из моих проектов, которые похожи на агрегатор, я разбираю фиды, подкасты и т.д. из Интернета.

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

foreach(feed in feeds)
{
   read_from_web(feed)
   parse(feed)
}

Итак, я хочу реализовать concurrency и не мог решить, должен ли я в основном использовать ThreadPools для обработки с рабочими потоками или просто полагаться на TPL, чтобы отсортировать его.

ThreadPools наверняка обработает задание для меня с рабочими потоками, и я получу то, что ожидаю (и в многоядерных средах ЦП также будут использованы и другие ядра).

concurrency

Но я все еще хочу рассматривать TPL тоже, поскольку он рекомендует метод, но я немного обеспокоен этим. Прежде всего, я знаю, что TPL использует ThreadPools, но добавляет дополнительный уровень принятия решений. Я в основном обеспокоен условием присутствия одноядерной среды. Если я не ошибаюсь, TPL начинается с числа рабочих потоков, равных числу доступных CPU-ядер в самом начале. Я боюсь, что TPL даст похожие результаты для последовательного подхода к моему делу с привязкой к IO.

Итак, для операций с привязкой IO (в моем случае чтения ресурсов из Интернета) лучше всего использовать ThreadPools и управлять вещами, или лучше просто полагаться на TPL? Может ли TPL также использоваться в сценариях, связанных с IO?

Обновление. Моя главная проблема заключается в том, что в одноядерном процессоре среда будет TPL просто вести себя как последовательный подход или будет ли она предлагать concurrency? Я уже читаю Параллельное программирование с помощью Microsoft.NET, и поэтому книги, но не смог найти точного ответа для этого.

Примечание: это повторная фраза моего предыдущего вопроса [Можно ли использовать thread- concurrency и parallelism вместе?], что было довольно неправильно сформулировано.

Ответ 1

Итак, я решил написать тесты для этого и увидеть его на практических данных.

Test Legend

  • Ит: Итерация
  • Seq: Последовательный подход.
  • PrlEx: параллельные расширения - Parallel.ForEach
  • TPL: параллельная библиотека задач
  • TPool: ThreadPool

Результаты тестирования

Одноядерный процессор [Win7-32] - работает под VMWare -

Test Environment: 1 physical cpus, 1 cores, 1 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.82s  04.05s  02.69s  02.60s
#2      07.48s  03.18s  03.17s  02.91s
#3      07.66s  03.21s  01.90s  01.68s
#4      07.43s  01.65s  01.70s  01.76s
#5      07.81s  02.20s  01.75s  01.71s
#6      07.67s  03.25s  01.97s  01.63s
#7      08.14s  01.77s  01.72s  02.66s
#8      08.04s  03.01s  02.03s  01.75s
#9      08.80s  01.71s  01.67s  01.75s
#10     10.19s  02.23s  01.62s  01.74s
________________________________________________________________________________

Avg.    08.40s  02.63s  02.02s  02.02s
________________________________________________________________________________

Одноядерный процессор [WinXP] - работает под VMWare -

Test Environment: 1 physical cpus, NotSupported cores, NotSupported logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.79s  04.05s  02.75s  02.13s
#2      07.53s  02.84s  02.08s  02.07s
#3      07.79s  03.74s  02.04s  02.07s
#4      08.28s  02.88s  02.73s  03.43s
#5      07.55s  02.59s  03.99s  03.19s
#6      07.50s  02.90s  02.83s  02.29s
#7      07.80s  04.32s  02.78s  02.67s
#8      07.65s  03.10s  02.07s  02.53s
#9      10.70s  02.61s  02.04s  02.10s
#10     08.98s  02.88s  02.09s  02.16s
________________________________________________________________________________

Avg.    08.46s  03.19s  02.54s  02.46s
________________________________________________________________________________

Двухъядерный процессор [Win7-64]

Test Environment: 1 physical cpus, 2 cores, 2 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      07.09s  02.28s  02.64s  01.79s
#2      06.04s  02.53s  01.96s  01.94s
#3      05.84s  02.18s  02.08s  02.34s
#4      06.00s  01.43s  01.69s  01.43s
#5      05.74s  01.61s  01.36s  01.49s
#6      05.92s  01.59s  01.73s  01.50s
#7      06.09s  01.44s  02.14s  02.37s
#8      06.37s  01.34s  01.46s  01.36s
#9      06.57s  01.30s  01.58s  01.67s
#10     06.06s  01.95s  02.88s  01.62s
________________________________________________________________________________

Avg.    06.17s  01.76s  01.95s  01.75s
________________________________________________________________________________

Четырехъядерный процессор [Win7-64] - Поддерживается HyprerThreading -

Test Environment: 1 physical cpus, 4 cores, 8 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.56s  02.03s  01.71s  01.69s
#2      07.42s  01.63s  01.71s  01.69s
#3      11.66s  01.69s  01.73s  01.61s
#4      07.52s  01.77s  01.63s  01.65s
#5      07.69s  02.32s  01.67s  01.62s
#6      07.31s  01.64s  01.53s  02.17s
#7      07.44s  02.56s  02.35s  02.31s
#8      08.36s  01.93s  01.73s  01.66s
#9      07.92s  02.15s  01.72s  01.65s
#10     07.60s  02.14s  01.68s  01.68s
________________________________________________________________________________

Avg.    08.35s  01.99s  01.75s  01.77s
________________________________________________________________________________

Суммирование

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

Выполнение тестов самостоятельно

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

Обновление: Исправлена ​​ссылка источника.

Ответ 2

Если вы пытаетесь максимизировать пропускную способность для задач с привязкой к IO, вы абсолютно должны комбинировать традиционные API асинхронной обработки (APM) с вашей работой на основе TPL. API APM - единственный способ разблокировать поток ЦП, пока асинхронный обратный вызов ввода-вывода не ожидается. TPL предоставляет вспомогательный метод TaskFactory::FromAsync, чтобы помочь в объединении кода APM и TPL.

Ознакомьтесь с этим разделом .NET SDK в MSDN под названием TPL и традиционным асинхронным программированием .NET для получения дополнительной информации о том, как объединить эти два программирования для достижения асинхронной нирваны.

Ответ 3

Вы правы, что TPL удаляет часть вашего элемента управления, когда вы создаете свой собственный пул потоков. Но это правильно, если вы не хотите копать глубже. TPL позволяет создавать длительные задачи, которые не являются частью пула потоков TPL и могут хорошо служить вашей цели. Опубликованная книга, которая представляет собой бесплатное чтение Parallel Programming with Microsoft.NET, даст вам гораздо больше информации о том, как использовать TPL для использования. У вас всегда есть возможность предоставить Paralle.For, Tasks explicit параметры, сколько потоков должно быть выделено. Кроме того, вы можете заменить планировщик TPL своим собственным, если хотите получить полный контроль.

Ответ 5

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

Думаю, что так и будет. Что такое узкое место? Является синтаксический анализ или загрузка? Многопоточность не поможет вам с загрузкой из Интернета.

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

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

PS. "Ничего себе, моя функция выполняется в 0,7 с вместо 0,9";)

Ответ 6

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

var client = new HttpClient();
var urls = new[]{"a", "url", "to", "find"};

// due to the EAP pattern, this will run in parallel.
var tasks = urls.Select(c=> client.GetAsync(c));

var result = Tasks.WhenAll(task).ContinueWith(a=> AnalyzeThisWords(a.Result));
result.Wait(); // don't know if this is needed or it correct to call wait

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

При использовании EAP количество задач не связано с количеством потоков.

Поскольку вы полагаетесь на задачу GetAsync, клиент http использует сетевой поток (сокет, клиент tcp или что-то еще) и сигнализирует его, чтобы поднять событие при выполнении BeginRead/EndRead. Таким образом, ни один поток не участвует в этом моменте.

После вызова завершения, возможно, создается новый поток, но он работает с TaskScheduler (используется в вызове GetAsync/ContinueWith call), чтобы создать новый поток, использовать существующий поток или встроить задачу для использования вызывающего потока.

Если AnalyzeThisWords блокирует слишком много времени, вы начинаете получать узкие места, поскольку "обратный вызов" в ContinueWith выполняется из рабочего пула потоков.