Что такое пул потоков
Перейти к содержимому

Что такое пул потоков

  • автор:

Пул управляемых потоков

Класс System.Threading.ThreadPool обеспечивает приложение пулом рабочих потоков, управляемых системой, позволяя пользователю сосредоточиться на выполнении задач приложения, а не на управлении потоками. Если имеются небольшие задачи, которые требуют фоновой обработки, пул управляемых потоков — это самый простой способ воспользоваться преимуществами нескольких потоков. В Framework 4 и более поздних версиях использовать пул потоков стало значительно проще, так как вы можете создавать объекты Task и Task , которые выполняют в потоках пула асинхронные задачи.

Платформа .NET использует потоки из пула в различных целях, в том числе для операций библиотеки параллельных задач (TPL), асинхронного ввода-вывода, обратных вызовов таймера, регистрируемых операций ожидания, асинхронного вызова методов с использованием делегатов и для подключения к сокетам System.Net.

Характеристики пула потоков

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

Для каждого процесса существует только один пул потоков.

Исключения в потоках из пула потоков

Необработанные исключения в потоках из пула приводят к завершению процесса. Есть три исключения из этого правила:

  • Исключение System.Threading.ThreadAbortException возникает в потоке пула вследствие вызова Thread.Abort.
  • Исключение System.AppDomainUnloadedException возникает в потоке пула вследствие выгрузки домена приложения.
  • Среда CLR или процесс ведущего приложения прерывает выполнение потока.

Максимальное число потоков в пуле потоков

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

Вы можете управлять максимальным количеством потоков с помощью методов ThreadPool.GetMaxThreads и ThreadPool.SetMaxThreads.

В коде, содержащем среду CLR, этот размер можно задать с помощью метода ICorThreadpool::CorSetMaxThreads .

Минимальные значения пула потоков

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

Если потребность низкая, фактическое количество потоков из пула потоков может быть ниже минимальных значений.

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

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

Использование пула потоков

Самым простым способом использования пула потоков является применение библиотеки параллельных задач (TPL). По умолчанию такие типы TPL, как Task и Task , используют потоки из пула для выполнения задач.

Пул потоков также можно использовать путем вызова ThreadPool.QueueUserWorkItem из управляемого кода (или ICorThreadpool::CorQueueUserWorkItem из неуправляемого кода) и передачи делегата System.Threading.WaitCallback, представляющего метод, который выполняет задачу.

Другим способом использования пула потоков является помещение в очередь рабочих элементов, которые имеют отношение к операции ожидания, с помощью метода ThreadPool.RegisterWaitForSingleObject и передача дескриптора System.Threading.WaitHandle, который вызывает метод, представленный делегатом System.Threading.WaitOrTimerCallback, при получении сигнала или истечении времени ожидания. Потоки из пула потоков используются для вызова методов обратного вызова.

Примеры см. по ссылкам на страницы API.

Пропуск проверок безопасности

Пул потоков также предоставляет методы ThreadPool.UnsafeQueueUserWorkItem и ThreadPool.UnsafeRegisterWaitForSingleObject. Используйте эти методы только в том случае, если вы уверены, что стек вызывающего объекта не важен для проверок безопасности, осуществляемых во время выполнения задачи в очереди. ThreadPool.QueueUserWorkItem и ThreadPool.RegisterWaitForSingleObject перехватывают стек вызывающего объекта, который объединяется со стеком потока из пула потоков, когда поток начинает выполнять задачу. Если требуется проверка безопасности, проверяется весь стек. Несмотря на обеспечение безопасности, такая проверка также влияет на производительность.

Когда не следует использовать потоки из пула потоков

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

  • Необходим основной поток.
  • Поток должен иметь определенный приоритет.
  • Имеются задачи, которые приводят к блокировке потока на длительное время. Для пула потоков определено максимальное количество потоков, поэтому большое число заблокированных потоков в пуле может препятствовать запуску задач.
  • Необходимо поместить потоки в однопотоковое подразделение. Все потоки ThreadPool находятся в многопотоковом подразделении.
  • Необходимо иметь постоянное удостоверение, сопоставленное с потоком, или назначить поток задаче.

См. также раздел

  • System.Threading.ThreadPool
  • System.Threading.Tasks.Task
  • System.Threading.Tasks.Task
  • Библиотека параллельных задач (TPL)
  • Практическое руководство. Возвращение значения из задачи
  • Объекты и функциональные возможности работы с потоками
  • Потоки и работа с потоками
  • Asynchronous File I/O
  • Таймеры

Совместная работа с нами на GitHub

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

Пул потоков 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("цикл , выполнение внутри потока из пула ", i, Thread.CurrentThread.ManagedThreadId); Thread.Sleep(50); > > > > 

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

Пример использования пулов потоков

Здесь может возникнуть вопрос: в чем же преимущество использования поддерживаемого CLR пула потоков по сравнению с явным созданием объектов Thread? Ниже перечислены эти преимущества:

  • Пул потоков управляет потоками эффективно, уменьшая количество создаваемых, запускаемых и останавливаемых потоков.
  • Используя пул потоков, можно сосредоточиться на решении задачи, а не на инфраструктуре потоков приложения.

Тем не менее, в некоторых случаях предпочтительно ручное управление потоками. Пулы потоков очень просты в применении, однако обладают рядом ограничений, которые перечислены ниже:

  • Все потоки в пуле потоков являются фоновыми. В случае завершения работы всех приоритетных потоков в процессе работа всех фоновых потоков тоже останавливается. Сделать поток из пула приоритетным не удастся.
  • Нельзя изменять приоритет или имя находящего в пуле потока. Все потоки в пуле представляют собой потоки многопоточного апартамента (multi-threaded apartment — МТА), а многие СОМ-объекты требуют использования потоков однопоточного апартамента (single-threaded apartment — STA).
  • Потоки в пуле подходят для выполнения только коротких задач. Если необходимо, чтобы поток функционировал все время (как, например, поток средства проверки орфографии в Word), его следует создавать с помощью класса Thread.
  • Нельзя создать поток с фиксированной идентичностью, чтобы можно было прерывать его или находить по имени.

Как работает пул потоков?

В .NET есть пул потоков, — это заготовленные потоки, готовые к выполнению какой-то задачи. Но при ручном создании потока нету возможности создать поток без вызова (т.е. на будущее) делегата и более того, если поток отработал, то ему нельзя дать другую задачу. Так как эта магия происходит? Даже в WinApi, вроде, нету функции, которой можно было бы передать уже созданному потоку некоторое задание.

Отслеживать
1,250 3 3 золотых знака 23 23 серебряных знака 51 51 бронзовый знак
задан 26 дек 2019 в 14:03
1 1 1 серебряный знак 1 1 бронзовый знак

В .NET пул потоков контроллируется самой библиотекой, вы работаете с потоками с помощью стандартных ф-ций, и не беспокоитесь о пуле, библиотека автоматически ведёт пул.

26 дек 2019 в 14:06

@nick_n_a это понятно. Но мне интересно, как заранее заготовленному потоку передается моя задача, если Thread не имеет для этого спец. методов, а в WinApi, вроде, функций нету для привязки задачи к существующему потоку.

26 дек 2019 в 14:09
Более детально кажись тут docs.microsoft.com/ru-ru/dotnet/api/system.threading.threadpool
26 дек 2019 в 14:11

Библиотека нет — обверточная. В Thread будет передана ф-ция «прокладка», которая может подобрать любую «задачу» (как вы её назвали). Вы не видя код запуска Thread не можете сказать что он выполняет, вот в этот код и добавлено доп-функции.

26 дек 2019 в 14:13

Нет, условно говоря, коллекция свободных потоков (пул) из неё выбирается «свободный» поток, которому даётся именно то задание («задача»), через делегат, которое вы прямо сейчас требуете выполнить в паралеле. Выполнение выбраного потока «размораживается» (thread resume, либо через WaitForSingleObject не смотрел детально как сделано) после чего он после разморозки через делегат получит «задачу» , а паралельное выполнение подхватывает уже ОС.

26 дек 2019 в 14:28

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

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

ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomething)); 

Для более гибкой работы с потоками есть Task(Задача), которая может использовать так называемое продолжение (ContinueWith)

Task task = new Task(new Action(Method1)); Task continue = task.ContinueWith(new Action(Method2)); 

Так же у задачи есть возможность «подготовки» к выполнению + отложенный стар.

Task task = new Task(new Action(DoSomething), TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning); 

«Холодный» запуск задачи

Task task = new Task(new Action(Method1)); //что-то делаем Task.Start(); 

«Горячий» запуск задачи

Task.Run(new Action(Method)); 

Выполнение в основном потоке

Task task = new Task(new Action(Method)); Tast.RunSynchronously(); 

ну и нововведение ValueTask — обертка над самой задачей

System.Treading.Tasks.Extensions 

Пулы потоков

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

Архитектура пула потоков

Использование пула потоков может быть полезным для следующих приложений:

  • Приложение с высокой степенью параллелизма и может асинхронно отправлять большое количество небольших рабочих элементов (например, распределенный поиск по индексу или сетевой ввод-вывод).
  • Приложение, которое создает и уничтожает большое количество потоков, каждый из которых выполняется в течение короткого времени. Использование пула потоков может снизить сложность управления потоками и снизить затраты, связанные с созданием и уничтожением потоков.
  • Приложение, которое обрабатывает независимые рабочие элементы в фоновом режиме и параллельно (например, загружает несколько вкладок).
  • Приложение, которое должно выполнять монопольное ожидание объектов ядра или блокировать входящие события в объекте. Использование пула потоков может снизить сложность управления потоками и повысить производительность за счет уменьшения количества переключений контекста.
  • Приложение, которое создает пользовательские потоки ожидания для ожидания событий.

Исходный пул потоков был полностью перепроектирован в Windows Vista. Новый пул потоков улучшен, так как он предоставляет один тип рабочего потока (поддерживает как операции ввода-вывода, так и другие), не использует поток таймера, предоставляет одну очередь таймера и выделенный постоянный поток. Он также предоставляет группы очистки, более высокую производительность, несколько пулов для каждого процесса, которые планируются независимо, и новый API пула потоков.

Архитектура пула потоков состоит из следующих компонентов:

  • Рабочие потоки, выполняющие функции обратного вызова
  • Потоки официанта, ожидающие нескольких дескрипторов ожидания
  • Рабочая очередь
  • Пул потоков по умолчанию для каждого процесса
  • Фабрика рабочих ролей, управляющая рабочими потоками

Рекомендации

Новый API пула потоков обеспечивает большую гибкость и управление, чем исходный API пула потоков. Однако есть несколько незначительных, но важных различий. В исходном API сброс ожидания был автоматическим; в новом API ожидание должно быть явно сброшено каждый раз. Исходный API автоматически обрабатывал олицетворение, передав контекст безопасности вызывающего процесса в поток. При использовании нового API приложение должно явно задать контекст безопасности.

Ниже приведены рекомендации по использованию пула потоков.

  • Потоки процесса совместно используют пул потоков. Один рабочий поток может выполнять несколько функций обратного вызова по одной за раз. Этими рабочими потоками управляет пул потоков. Поэтому не следует завершать поток из пула потоков путем вызова TerminateThread в потоке или метода ExitThread из функции обратного вызова.
  • Запрос ввода-вывода может выполняться в любом потоке в пуле потоков. Для отмены ввода-вывода в потоке пула потоков требуется синхронизация, так как функция отмены может выполняться в потоке, отличном от потока, обрабатывающего запрос ввода-вывода, что может привести к отмене неизвестной операции. Чтобы избежать этого, всегда укажите структуру OVERLAPPED , с которой был инициирован запрос ввода-вывода при вызове CancelIoEx для асинхронного ввода-вывода, или используйте собственную синхронизацию, чтобы убедиться, что другие операции ввода-вывода не могут быть запущены в целевом потоке перед вызовом функции CancelSynchronousIo или CancelIoEx .
  • Очистите все ресурсы, созданные в функции обратного вызова, перед возвратом из функции. К ним относятся TLS, контексты безопасности, приоритет потока и регистрация COM. Функции обратного вызова также должны восстановить состояние потока перед возвратом.
  • Поддерживайте дескриптор ожидания и связанные с ними объекты, пока пул потоков не сообщит о завершении работы с дескриптором.
  • Пометьте все потоки, ожидающие длительных операций (таких как очистка операций ввода-вывода или очистка ресурсов), чтобы пул потоков мог выделять новые потоки вместо этого.
  • Перед выгрузкой библиотеки DLL, которая использует пул потоков, отмените все рабочие элементы, операции ввода-вывода, операции ожидания и таймеры и дождитесь завершения обратных вызовов.
  • Избегайте взаимоблокировок, устраняя зависимости между рабочими элементами и обратными вызовами, гарантируя, что обратный вызов не ожидает завершения, а также сохраняя приоритет потока.
  • Не помещайте слишком много элементов в очередь слишком быстро в процессе с другими компонентами, используя пул потоков по умолчанию. Для каждого процесса существует один пул потоков по умолчанию, включая Svchost.exe. По умолчанию каждый пул потоков имеет не более 500 рабочих потоков. Пул потоков пытается создать больше рабочих потоков, если число рабочих потоков в состоянии готовности или выполнения должно быть меньше числа процессоров.
  • Избегайте модели однопотокового подразделения COM, так как она несовместима с пулом потоков. STA создает состояние потока, которое может повлиять на следующий рабочий элемент потока. STA, как правило, является долгосрочным и имеет сходство потоков, которое является противоположностью пула потоков.
  • Создайте новый пул потоков для управления приоритетом и изоляцией потоков, создания пользовательских характеристик и, возможно, повышения скорости реагирования. Однако для дополнительных пулов потоков требуется больше системных ресурсов (потоков, памяти ядра). Слишком много пулов увеличивает вероятность состязания за ЦП.
  • По возможности используйте объект с возможностью ожидания вместо механизма на основе APC, чтобы сообщить потоку пула потоков. APC не работают так же хорошо с потоками пула потоков, как и другие механизмы сигнализации, так как система управляет временем существования потоков пула потоков, поэтому поток может быть прерван до доставки уведомления.
  • Используйте расширение отладчика пула потоков ! tp. Эта команда используется следующим образом:
    • флаги адресовпула
    • Флагиадреса obj
    • флагиадресов tqueue
    • адрес официанта
    • рабочий адрес

    Для пула, официанта и рабочей роли, если адрес равен нулю, команда создает дампы всех объектов. Для официанта и рабочей роли пропуск адреса создает дамп текущего потока. Определены следующие флаги: 0x1 (однострочный вывод), 0x2 (члены дампа) и 0x4 (рабочая очередь пула дампов).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *