Пул потоков CLR
88C# и .NET --- Многопоточность и файлы --- Пул потоков CLR
Создание потоков требует времени. Если есть различные короткие задачи, подлежащие выполнению, можно создать набор потоков заранее и затем просто отправлять соответствующие запросы, когда наступает очередь для их выполнения. Было бы неплохо, если бы количество этих потоков автоматически увеличивалось с ростом потребности в потоках и уменьшалось при возникновении потребности в освобождении ресурсов.
Создавать подобный список потоков самостоятельно не понадобится. Для управления таким списком предусмотрен класс ThreadPool, который по мере необходимости уменьшает и увеличивает количество потоков в пуле до максимально допустимого. Значение максимально допустимого количества потоков в пуле может изменяться. В случае двуядерного ЦП оно по умолчанию составляет 1023 рабочих потоков и 1000 потоков ввода-вывода.
Можно указывать минимальное количество потоков, которые должны запускаться сразу после создания пула, и максимальное количество потоков, доступных в пуле. Если остались какие-то подлежащие обработке задания, а максимальное количество потоков в пуле уже достигнуто, то более новые задания будут помещаться в очередь и там ожидать, пока какой-то из потоков завершит свою работу.
Чтобы запросить поток из пула для обработки вызова метода, можно использовать метод QueueUserWorkItem(). Этот метод перегружен, чтобы в дополнение к экземпляру делегата WaitCallback позволить указывать необязательный параметр System.Object для специальных данных состояния.
Ниже приведен пример приложения, в котором сначала читается и выводится на консоль информация о максимальном количестве рабочих потоков и потоков ввода-вывода. Затем в цикле for метод JobForAThread() назначается потоку из пула потоков за счет вызова метода ThreadPool.QueueUserWorkltem() и передачи делегата типа WaitCallback. Пул потоков получает этот запрос и выбирает из пула один из потоков для вызова метода. Если пул еще не существует, он создается и запускается первый поток. Если же пул существует и в нем имеется один свободный поток, задание переадресовывается этому потоку:
using System;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
int nWorkerThreads;
int nCompletionThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionThreads);
Console.WriteLine("Максимальное количество потоков: " + nWorkerThreads
+ "\nПотоков ввода-вывода доступно: " + nCompletionThreads);
for (int i = 0; i < 5; i++)
ThreadPool.QueueUserWorkItem(JobForAThread);
Thread.Sleep(3000);
Console.ReadLine();
}
static void JobForAThread(object state)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("цикл {0}, выполнение внутри потока из пула {1}",
i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(50);
}
}
}
}
После запуска этого приложения оказывается, что при текущих параметрах можно использовать 1023 рабочих потоков. Пять заданий обрабатываются всего лишь двумя потоками из пула. У вас эти результаты могут выглядеть и по-другому. Кроме того, если изменить время бездействия для заданий и само количество подлежащих обработке заданий, то результаты еще больше будут отличаться.
Здесь может возникнуть вопрос: в чем же преимущество использования поддерживаемого CLR пула потоков по сравнению с явным созданием объектов Thread? Ниже перечислены эти преимущества:
Пул потоков управляет потоками эффективно, уменьшая количество создаваемых, запускаемых и останавливаемых потоков.
Используя пул потоков, можно сосредоточиться на решении задачи, а не на инфраструктуре потоков приложения.
Тем не менее, в некоторых случаях предпочтительно ручное управление потоками. Пулы потоков очень просты в применении, однако обладают рядом ограничений, которые перечислены ниже:
Все потоки в пуле потоков являются фоновыми. В случае завершения работы всех приоритетных потоков в процессе работа всех фоновых потоков тоже останавливается. Сделать поток из пула приоритетным не удастся.
Нельзя изменять приоритет или имя находящего в пуле потока. Все потоки в пуле представляют собой потоки многопоточного апартамента (multi-threaded apartment — МТА), а многие СОМ-объекты требуют использования потоков однопоточного апартамента (single-threaded apartment — STA).
Потоки в пуле подходят для выполнения только коротких задач. Если необходимо, чтобы поток функционировал все время (как, например, поток средства проверки орфографии в Word), его следует создавать с помощью класса Thread.
Нельзя создать поток с фиксированной идентичностью, чтобы можно было прерывать его или находить по имени.