Concurrency::parallel_for (PPL) создает слишком много потоков

Я использую Concurrency::parallel_for() библиотеки параллельных шаблонов Visual Studio 2010 (PPL) для обработки индексированного набора задач (как правило, индексный набор намного больше, чем количество потоков, которые могут выполняться одновременно). Каждая задача, прежде чем выполнять длительные вычисления, начинается с запроса частного ресурса рабочего хранилища у диспетчера общих ресурсов (в случае: представление файла с отображением памяти для конкретной задачи, но я думаю, что сюжетная линия была бы такой же, если бы каждая задача запросил выделение частной памяти из общей кучи).

Использование диспетчера общих ресурсов синхронизируется с Concurrency::critical_section, и здесь начинается проблема: если первый поток/задача находится в критической секции, а вторая задача делает запрос, она должна ждать, пока запрос первой задачи не будет обработан. Затем PPL, по-видимому, думает: эй, этот поток ожидает, и есть еще задачи, поэтому создается другой поток, вызывающий до 870 потоков, в основном ожидающих в одном и том же диспетчере ресурсов.

Теперь, поскольку обработка запроса ресурсов является лишь небольшой частью всей задачи, я хотел бы сказать PPL в этой части, чтобы он придержал своих лошадей, ни один из ожиданий или совместных блоков не должен вызывать запуск новых потоков из указанного раздела. work-thread, и мой вопрос заключается в следующем: можно ли и как запретить определенному разделу потока создавать новые потоки, даже если он совместно блокируется. Я бы не возражал против создания новых потоков в других блоках дальше по пути обработки потока, но не более, чем, скажем, 2 * количество (гипер) ядер.

Альтернативы, которые я рассматривал до сих пор:

  1. Ставьте задачи в очередь и обрабатывайте очередь из ограниченного числа потоков. Проблема: я надеялся, что parallel_for PPL сделает это сам.

  2. Определите Concurrency::combinable<Resource> resourceSet; вне Concurrency::parallel_for и инициализируйте resourceSet.local() один раз, чтобы уменьшить количество запросов ресурсов (за счет повторного использования ресурсов) до количества потоков (которое должно быть меньше количества задач). Проблема: эта оптимизация не предотвращает создание лишнего потока.

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

Я прочитал http://msdn.microsoft.com/en-us/library/ff601930.aspx, раздел "Не блокировать повторно в параллельном цикле", но следуя приведенным здесь советам, параллельные потоки вообще отсутствовать.


person Maarten Hilferink    schedule 30.09.2014    source источник
comment
Я только что нашел stackoverflow.com/questions/9990363/, что очень похоже на этот вопрос.   -  person Maarten Hilferink    schedule 02.10.2014


Ответы (2)


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

Однако у меня есть несколько советов, которые могут оказаться полезными для смягчения проблемы, пусть и не идеальным способом:

  • Вместо critical_section используйте несовместный примитив синхронизации для защиты диспетчера ресурсов. Я думаю (хотя и не проверял), что классический WinAPI CRITICAL_SECTION должен преуспевать. В качестве радикального шага в этом направлении вы можете рассмотреть возможность использования других параллельных библиотек для своего кода; например Intel TBB предоставляет большую часть PPL API и имеет больше (отказ от ответственности: я связан с ним).

  • Предварительно выделить ряд ресурсов вне параллельного цикла. Один ресурс на задачу не нужен; одного на поток должно быть достаточно. Поместите эти ресурсы в concurrent_queue, а внутри задачи извлеките ресурс из очереди, используйте, а затем отправьте обратно. Кроме того, вместо возврата ресурса в очередь поток может сохранить его внутри объекта combinable для повторного использования в других задачах. Если очередь окажется пустой (например, если PPL переподписывает машину), могут быть разные подходы, например. вращение в цикле до тех пор, пока какой-либо другой поток не вернет ресурс или не запросит другой ресурс у менеджера. Также вы можете предварительно выделить больше ресурсов, чем количество потоков, чтобы свести к минимуму вероятность исчерпания ресурсов.

person Alexey Kukanov    schedule 30.09.2014
comment
Спасибо, я рассмотрю и протестирую несовместную синхронизацию (хотя мой менеджер ресурсов также используется в других контекстах, в которых желательна совместная синхронизация), TBB, а также boost::thread. - person Maarten Hilferink; 02.10.2014
comment
Я думаю, что выталкивание ресурсов из очереди внутри задачи также запускает новые потоки, когда очередь пуста и (совместно) блокируется, а другие задачи все еще ждут запуска. - person Maarten Hilferink; 02.10.2014
comment
concurrent_queue не имеет блокирующего всплывающего метода, только try_pop(), который немедленно возвращает значение, если очередь пуста (см. msdn.microsoft.com/en-us/library/ee355358.aspx). Так что вы можете, например. сделать вращающийся цикл, который вызывает try_pop() до успеха. - person Alexey Kukanov; 02.10.2014
comment
Итак, если бы я перешел на TBB, как я мог бы запретить определенному разделу потока (часть лямбда-функции, которую я бы предоставил parallel_for) создавать новые потоки, даже если он совместно блокируется? - person Maarten Hilferink; 02.10.2014
comment
В TBB кооперативная синхронизация не используется. - person Alexey Kukanov; 02.10.2014
comment
В случае недостаточной подписки из-за большого количества блокировок в параллельных задачах мы рекомендуем пользователям инициализировать TBB с большим количеством рабочих потоков. Кроме того, поскольку синхронизация в TBB не является кооперативной, а планировщик задач не знает об удерживаемой блокировке, не рекомендуется использовать вложенную параллельную конструкцию внутри критической секции. Так что могут быть некоторые проблемы, если ваш менеджер ресурсов использует параллелизм внутри. - person Alexey Kukanov; 02.10.2014

Мой ответ — не «то» решение с использованием PPL, но я думаю, что вы могли бы сделать это так легко с пулом потоков, например taskqueue вам следует взглянуть на этот ответ.

Таким образом, вы заполняете очередь своими работами, это гарантирует, что параллельно будет работать не более 'x' задач, где x равно boost::thread::hardware_concurrency() (да, снова повысить...)

person Jean Davy    schedule 01.10.2014
comment
Спасибо, я еще раз взгляну на boost::threads, хотя, похоже, у него нет parallel_for, поэтому мне пришлось бы самому разделить набор задач на ограниченный набор диапазонов задач. - person Maarten Hilferink; 02.10.2014