Использование ThreadPool внутри циклов в C # - PullRequest
4 голосов
/ 15 марта 2011

Я не слишком разбираюсь с потоками, но допустим ли следующий код (меня больше беспокоит использование пулов потоков внутри цикла):

      string[] filePaths = GetFilePaths();

      foreach (string filePath in filePaths )
      {
        ThreadPool.QueueUserWorkItem(DoStuff, filePath); 
      }

Есть ли другой способ, которым это можетбыть готовым?

РЕДАКТИРОВАТЬ:

NB Каждое выполнение DoStuff, в свою очередь, создает несколько подпотоков (около 200).Эти подпотоки имитируют пользователей системы и отвечают только за получение и отправку информации по TCP.

Ответы [ 4 ]

3 голосов
/ 15 марта 2011

Ваш текущий код может пойти не так, если удерживается комбинация из двух следующих элементов:

  • файлов очень много
  • обработка файла (DoStuff) занимает значительное количествоtime

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

Если вы можете использовать Fx4, используйте TPL.

Для более ранних версий переписать свой код, чтобы использовать меньшее количество потоков.


Правка, поскольку вы используете Fx4:

Наибольший выигрыш может быть при использовании System.Directory.EnumFiles() вместо Directory.GetFiles().

Эскиз:

var files = System.Directory.EnumerateFiles(...);  // deferred execution

Parallel.ForEach(files, f => DoStuff(f));  // maybe use MaxDegree or CancelationToken

// all files done here

Вы также можете обернуть это .ForEach в (одиночной) попытке / захвате и т. Д.

И если DoStuff() требуется параллелизм, вы должны использоватьTPL также, возможно, передавая CancellationToken и т. д. Это поставило бы весь параллелизм под контроль одного планировщика.

Возможно, вам придется помочь в тонкой настройке, но это тоже будет намного проще, чем без TPL.

2 голосов
/ 15 марта 2011

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

string[] filePaths = GetFilePaths();
ThreadPool.QueueUserWorkItem(DoStuff, filePaths);

иположить foreach внутрь DoStuff.Это может быть приемлемым решением в зависимости от значений, которые вы ожидаете получить filePaths (например, если все пути находятся на одном устройстве, попытка сделать все сразу не будет быстрее, а может даже медленнее), и это определенносамое простое.

Если вы определенно хотите выполнять их параллельно, то вам следует обратиться к параллельной библиотеке задач (только .NET 4) и, в частности, Parallel.ForEach.Поскольку ограничение количества максимально одновременных задач является хорошей идеей, вот пример, который показывает, как вы можете это сделать:

var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
Parallel.ForEach(filePaths, options, i=> {
    DoStuff(i);
});
2 голосов
/ 15 марта 2011

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

Начиная с .NET 4, есть несколько очень простых альтернатив использованию пула потоков.

Parallel.ForEach будет актуально для вас здесь.Вот ссылка на новые функции параллелизма в .NET 4:

http://galratner.com/blogs/net/archive/2010/04/24/a-quick-lap-around-net-4-0-s-parallel-features.aspx

Обновление: на момент вашего редактирования с упоминанием 200 подпотоков.Я бы посоветовал, чтобы потоки ОС не были легковесными объектами.С ними связаны накладные расходы, и они могут быстро компенсировать любые выгоды от параллелизма.Параллельное выполнение некоторой задачи требует некоторого рассмотрения того, сколько работы происходит, какова цель (освободить пользовательский интерфейс, использовать все ядра и т. Д.), А также то, является ли возможная работа интенсивной ЦП или IO.Есть и другие факторы, но я считаю их весьма важными.Я хотел бы создать еще один вопрос SO, описывающий то, что вы пытаетесь решить, используя этот уровень параллелизма, чтобы вы могли получить некоторые советы по проектированию, более специфичные для вашей проблемы.

0 голосов
/ 15 марта 2011

Почему бы вам не инкапсулировать свои вызовы в Action<T> или delegate и не поместить их в многопоточную очередь в вашем цикле. Затем вы можете запустить (число) поток (ы), который будет работать, пока все действия из очереди не будут выполнены; Таким образом, вы можете контролировать количество используемых потоков, и вам не придется беспокоиться о том, что их будет слишком много.

...