Учитывает ли параллельная задача библиотека (или PLINQ) другие процессы

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

Например:

I Получено около 100 мультимедийных файлов, которые необходимо кодировать или транскодировать (например, из WAV в FLAC или из FLAC в MP3). Кодирование выполняется путем запуска внешнего процесса (например, FLAC.EXE или LAME.EXE). Каждый файл занимает около 30 секунд. Каждый процесс в основном связан с процессором, но там есть некоторые операции ввода-вывода. У меня есть 4 ядра, поэтому в худшем случае (транскодирование путем передачи декодера в кодер) все еще используются только 2 ядра. Я хотел бы сделать что-то вроде:

Parallel.ForEach(sourceFiles,
    sourceFile =>
        TranscodeUsingPipedExternalProcesses(sourceFile));

Будет ли это запустить 100 задач (и, следовательно, 200 внешних процессов, конкурирующих за процессор)? Или он увидит, что процессор занят и делает только 2-3 за один раз?

14
задан Roger Lipscombe 16 August 2010 в 08:11
поделиться

2 ответа

Здесь вы столкнетесь с парой проблем. Механизм предотвращения голода планировщика будет рассматривать ваши задачи как заблокированные, поскольку они ожидают процессы. Ему будет трудно отличить заблокированный поток от просто ожидающего завершения процесса. В результате он может запланировать новые задачи, если ваши задачи выполняются долгое время (см. ниже). Эвристика hillclimbing должна учитывать общую нагрузку на систему, как от вашего приложения, так и от других. Она просто пытается максимизировать выполненную работу, поэтому она будет добавлять больше работы, пока общая пропускная способность системы не перестанет расти, а затем она отступит. Я не думаю, что это повлияет на ваше приложение, но проблема избегания остановки, вероятно, повлияет.

Более подробно о том, как все это работает, вы можете прочитать в Parallel Programming with Microsoft®.NET, Colin Campbell, Ralph Johnson, Ade Miller, Stephen Toub (ранний черновик есть онлайн).

"Пул потоков .NET автоматически управляет количеством рабочих потоков в пуле. потоков в пуле. Он добавляет и удаляет потоки в соответствии со встроенной эвристики. Пул потоков .NET имеет два основных механизма для введения нитей: механизм предотвращения голода, который добавляет рабочие потоки, если он не видит прогресса в работе с элементами очереди, и эвристика подъема на холм (hillclimbing). эвристика, которая пытается максимизировать пропускную способность, используя при этом как можно как можно меньше потоков.

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

Целью эвристики восхождения по холму является улучшение использования ядер, когда потоки блокируются вводом/выводом или другими условиями ожидания. которые останавливают процессор. По умолчанию управляемый пул потоков имеет один рабочий поток на ядро. Если один из этих рабочих потоков становится заблокированным, существует вероятность того, что ядро может быть недоиспользовано, в зависимости от в зависимости от общей нагрузки на компьютер. Логика внедрения потоков не делает различия между заблокированным потоком и потоком. который выполняет длительную, процессороемкую операцию. Поэтому, когда глобальная или локальная очередь пула потоков содержит отложенные рабочие элементы, активные рабочие элементы, которые требуют длительного времени для выполнения (более полсекунды) могут инициировать создание новых рабочих потоков пула потоков потоков.

Пул потоков .NET имеет возможность запускать потоки каждый когда рабочий элемент завершается или с интервалом в 500 миллисекунд, в зависимости от того, что короче. короче. Пул потоков использует эту возможность, чтобы попробовать добавить потоки (или убирая их), руководствуясь обратной связью от предыдущих изменений в количества потоков. Если кажется, что добавление потоков способствует повышению пропускной способности, пул потоков добавляет еще; в противном случае он уменьшает количество рабочих потоков. Эта техника называется эвристикой восхождения на холм. Таким образом, одна из причин, по которой отдельные задачи должны быть короткими, заключается в том, чтобы избежать "обнаружения голода", но другая причина держать их короткими, чтобы дать пулу потоков больше возможностей улучшить пропускную способность путем регулируя количество потоков. Чем короче продолжительность отдельных задачи, тем чаще пул потоков может измерять пропускную способность и и соответствующим образом корректировать количество потоков.

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

Что плохого в 500 рабочих потоках? В принципе, ничего, если у вас есть 500 ядер для их использования и огромное количество системной памяти. Фактически, это долгосрочное видение параллельных вычислений. Однако если у вас на компьютере нет такого количества ядер, вы оказываетесь окажетесь в ситуации, когда множество потоков конкурируют за временные срезы. Эта ситуация известна как переподписка процессора. Позволяя многим процессороемким потокам конкурировать за время на одном ядре добавляет накладные расходы на переключение контекста, что может сильно снизить общую производительность системы пропускную способность. Даже если у вас не закончится память, производительность в такой ситуации может быть намного, намного хуже, чем при последовательных вычислениях. (Каждое переключение контекста занимает от 6 000 до 8 000 процессорных циклов). Стоимость переключения контекста - не единственный источник накладных расходов. Управляемый поток в .NET занимает примерно мегабайт стека. независимо от того, используется ли это пространство для текущих выполняемых функций или нет. Для создания нового потока требуется около 200 000 циклов процессора, а для выхода из него - около 100 000 циклов. около 100 000 циклов для завершения работы потока. Это дорогостоящие операции.

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

Первый вариант - разделить ваше приложение на более короткие задачи, которые выполняются достаточно быстро, чтобы пул потоков мог успешно контролировать количество потоков для оптимальной пропускной способности. Вторая возможность - реализовать свой собственный планировщик задач который не выполняет внедрение потоков. Если ваши задачи имеют большую продолжительность, вам не нужен высоко оптимизированный планировщик задач, потому что стоимость планирования будет пренебрежимо мала по сравнению со временем выполнения времени выполнения задачи. В программе для разработчиков MSDN® есть пример простой реализации планировщика задач, который ограничивает максимальную степень параллелизма. Для получения дополнительной информации см. раздел "Дополнительное чтение". в конце этой главы.

В крайнем случае, вы можете использовать метод SetMaxThreads, чтобы сконфигурировать класс ThreadPool с верхним пределом для количества рабочих потоков, обычно равное количеству ядер (это свойство свойство Environment.ProcessorCount). Этот верхний предел применяется для всего процесса, включая все AppDomains."

.
21
ответ дан 1 December 2019 в 12:37
поделиться

Короткий ответ: нет.

Внутренне TPL использует стандартный ThreadPool для планирования своих задач. Итак, вы на самом деле спрашиваете, учитывает ли ThreadPool нагрузку на машину, а это не так. Единственное, что ограничивает количество одновременно выполняемых задач, - это количество потоков в пуле потоков, и ничего больше.

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

2
ответ дан 1 December 2019 в 12:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: