Какова хорошая стратегия для параллельной обработки очереди?

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

Я уже написал довольно тривиальный синхронный метод — первоначальное добавление корневого каталога в очередь, затем удаление каталога из очереди, добавление в очередь его подкаталогов и т. д., пока очередь не станет пустой. Я буду использовать ConcurrentQueue<T> для своей очереди, но уже понял, что мои петли остановятся преждевременно. Первый поток исключит корневой каталог из очереди, и сразу же все остальные потоки увидят, что очередь пуста, и выйдут, оставив первый поток единственным работающим. Я хотел бы, чтобы каждый поток зацикливался до тех пор, пока очередь не станет пустой, затем подождите, пока другой поток не поставит в очередь еще несколько каталогов, и продолжайте работу. Мне нужна какая-то контрольная точка в моем цикле, чтобы ни один из потоков не вышел, пока каждый поток не достигнет конца цикла, но я не уверен, что это лучший способ сделать это без взаимоблокировки, когда действительно больше нет каталогов для процесс.


person dlras2    schedule 08.05.2011    source источник
comment
Можете ли вы уточнить, что вы ищете в структуре папок? Просто имя файла или что-то внутри файла? Или даже что-то еще? Я думаю, что это жизненно важно, чтобы помочь вам с соответствующим алгоритмом   -  person Valentin Kuzub    schedule 08.05.2011
comment
Вы делаете это из соображений производительности? Я посмотрел на что-то подобное для процесса чтения тысяч файлов и обнаружил, что определяющим фактором производительности был дисковый ввод-вывод, а обработка в параллельных потоках не оказывала заметного влияния на производительность.   -  person ScruffyDuck    schedule 08.05.2011
comment
Я сопоставляю либо имя каталога, либо файлы внутри него с регулярным выражением, а затем возвращаю результаты - я ничего не сопоставляю внутри файлов. Я мог бы расширить его, чтобы проверить свойства файла, но это все.   -  person dlras2    schedule 08.05.2011
comment
@ScruffyDuck - Да, я делаю это из соображений производительности, но, как я уже сказал, на самом деле я не открываю файл, а просто проверяю его имена и свойства. Должно ли это сделать распараллеливание более эффективным?   -  person dlras2    schedule 08.05.2011
comment
Я бы подумал, что если вы выполняете минимальную обработку каждого файла, то это сделает использование нескольких потоков менее, чем более эффективным для повышения производительности. Мне кажется, что чем больше обработки вам нужно сделать для каждого файла, тем меньше будет влияние системного ввода-вывода и тем полезнее будет использование нескольких потоков для обработки файлов.   -  person ScruffyDuck    schedule 08.05.2011
comment
Убедитесь, что вы знаете о Directory.EnumerateFiles(). А если нужен параллелизм, TPL и BlockingCollection.   -  person Henk Holterman    schedule 08.05.2011


Ответы (3)


Используйте библиотеку параллельных задач.

Создайте Task для обработки первой папки. При этом создайте Task для обработки каждой подпапки (рекурсивно) и задачи для каждого соответствующего файла. Затем ждите выполнения всех задач для этой папки.

Среда выполнения TPL будет использовать пул потоков, избегая создания потоков, что является дорогостоящей операцией. для мелких работ.

Примечание:

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

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

person Richard    schedule 08.05.2011
comment
Если я правильно понимаю, я вообще отказываюсь от явной очереди и вызываю действия, а не ставлю в очередь подкаталоги? - person dlras2; 08.05.2011
comment
@Дэниел: Да. А точнее замена явной очереди на неявную (очередь рабочих элементов в пуле потоков). - person Richard; 08.05.2011
comment
Проблема, с которой я столкнулся при использовании этого метода, заключается в том, что я не знаю, как ожидать выполнения всех задач. WaitAll нужен определенный список задач для ожидания, которого у меня не будет, поскольку каждая задача создает больше задач. - person dlras2; 08.05.2011
comment
@Daniel: каждая задача ожидает задач, которые она создает напрямую, некоторые из этих задач сами будут ожидать задач - подумайте о дереве, в котором каждый узел рекурсивно ожидает своих прямых дочерних элементов. - person Richard; 09.05.2011

Если вы хотите придерживаться концепции явной очереди, взгляните на BlockingCollection< /а> класс. Метод GetConsumingEnumerable() возвращает IEnumerable, который блокируется, когда коллекция закончатся предметы и продолжится, как только появятся новые предметы. Это означает, что всякий раз, когда коллекция пуста, поток блокируется и, таким образом, предотвращает его преждевременную остановку.

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

person Theo Lenndorff    schedule 08.05.2011
comment
Это хорошая находка, но у меня возникли небольшие проблемы с ее адаптацией к моему сценарию «многие производители — многие потребители», поскольку ни один производитель не может окончательно сказать, что он закончен, пока другие производители производят. - person dlras2; 08.05.2011

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

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

person IAmTimCorey    schedule 08.05.2011
comment
Явное создание потоков почти всегда является неправильным ответом: либо пул потоков, либо задачи будут работать лучше. - person Richard; 08.05.2011
comment
Я думал об этом, но перевесят ли накладные расходы на создание всех потоков преимущества? Сопоставление файла или каталога не является сложной задачей, просто их может быть много. - person dlras2; 08.05.2011
comment
@Richard - Да, я бы использовал пул потоков и извлекал бы из него при необходимости. Я думал об этом, когда публиковал это, но, думаю, я не был ясен. Нет, вы не хотели бы создавать новые потоки с нуля, вместо этого вы хотели бы вытащить их из пула потоков и позволить им вернуться, когда они будут выполнены. - person IAmTimCorey; 08.05.2011