Когда использовать пул потоков в C#?

Я пытался изучить многопоточное программирование на C#, и я смущен тем, когда лучше использовать пул потоков против создания моих собственных потоков. Одна книга рекомендует использовать пул потоков только для небольших задач (что бы это ни значило), но я не могу найти никаких реальных рекомендаций. Какие соображения вы используете при принятии этого программного решения?

15 ответов


Если у вас есть много логических задач, требующих постоянной обработки, и вы хотите, чтобы это было сделано параллельно, используйте планировщик pool+.

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

Edit: о некоторых соображениях, я использую пулы потоков для доступа к базе данных, физики / моделирования, AI (игры), а также для скриптовых задач, выполняемых на виртуальных машинах, которые обрабатывают множество пользовательских задач.

обычно пул состоит из 2 потоков на процессор (так что, скорее всего, 4 в настоящее время), однако вы можете настроить количество потоков, которые вы хотите, если знаете, сколько вам нужно.

Edit: причина создания собственных потоков заключается в изменении контекста (это когда потоки должны меняться в процессе и из процесса вместе с их памятью). Имея бесполезные изменения контекста, скажите, когда вы не используете свои потоки, просто оставляя их сидеть, как можно было бы сказать, может легко половину производительности вашей программы (скажем, у вас есть 3 спящих потока и 2 активных потока). Таким образом, если эти потоки загрузки просто ждут, они съедают тонны процессора и охлаждают кэш для вашего реального приложения


Я бы предложил вам использовать пул потоков в C# по тем же причинам, что и любой другой язык.

Если вы хотите ограничить количество запущенных потоков или не хотите накладных расходов на их создание и уничтожение, используйте пул потоков.

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

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


вот хорошая сводка пула потоков в .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

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


Я настоятельно рекомендую прочитать эту бесплатную электронную книгу: Threading in C# by Joseph Albahari

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

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

  • параллельная библиотека задач (.NET Framework 4.0)
  • класса ThreadPool.Метод queueuserworkitem
  • Асинхронные Делегаты
  • BackgroundWorker

эта электронная книга объясняет все это и советует, когда их использовать против создания собственного потока.


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

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

пул потоков конструирован для того чтобы увеличить быть работы выполняется через процессоры (или ядра процессоров). Вот почему по умолчанию пул потоков запускает несколько потоков на процессор.

есть некоторые ситуации, где вы не хотели бы использовать пул потоков. Если вы ждете ввода-вывода или ждете события и т. д., Вы связываете этот поток пула потоков, и он не может использоваться кем-либо еще. Та же идея применима к длительным задачам, хотя то, что составляет длительную задачу, субъективно.

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

Примечание: Вы спросили об использовании потоке пула потоков для загрузки данных и выполнения операций ввода/вывода вы не должны использовать поток из пула потоков для этого (по причинам, которые я изложил выше). Вместо этого используйте асинхронный ввод-вывод (он же методы BeginXX и EndXX). Для FileStream Что будет BeginRead и EndRead. Для HttpWebRequest что будет BeginGetResponse и EndGetResponse. Они более сложны в использовании, но они являются правильным способом выполнения многопоточного ввода-вывода


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


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

использование пула потоков может иметь тонкие эффекты-некоторые таймеры .NET используют потоки пула потоков и не будут срабатывать, например.


для максимальной производительности при одновременном выполнении единиц напишите свой собственный пул потоков, в котором при запуске создается пул объектов потоков, и перейдите к блокировке (ранее приостановленной), ожидая запуска контекста (объект со стандартным интерфейсом, реализованным вашим кодом).

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

следует обсудить тот факт, что основной блок выполнения Windows (включая Windows 10) является потоком, а накладные расходы на переключение контекста ОС обычно незначительны. Проще говоря, я не смог найти убедительных доказательств многих из этих статей, утверждает Ли статья более высокую производительность, сохраняя переключение контекста или лучшее использование ЦП.

теперь немного реализма:

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

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

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

возможно, самый простой способ написать единицу выполнения-использовать задачу. Прелесть задания в том, что его можно создать и запустите его в строке вашего кода (хотя осторожность может быть гарантирована). Вы можете передать маркер отмены для обработки, когда вы хотите отменить задачу. Кроме того, он использует подход promise для цепочки событий, и вы можете вернуть определенный тип значения. Кроме того, с async и await существует больше опций, и ваш код будет более переносимым.

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

простой способ сравнить-запустить 512 потоков, 512 задач и 512 потоков ThreadPool. Вы найдете задержку в начале с потоками (следовательно, зачем писать пул потоков), но все 512 потоков будут запущены через несколько секунд, в то время как задачи и потоки .NET ThreadPool занимают до нескольких минут.

Ниже приведены результаты такого теста (четырехъядерный процессор i5 с 16 ГБ ОЗУ), дающий каждому 30 осталось несколько секунд. Выполняемый код выполняет простой ввод-вывод файлов на SSD-накопителе.

Результаты Теста


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

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

проверить этой страница на MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as (VS.80).aspx


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


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

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


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

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

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


Не забудьте исследовать фонового работника.

Я нахожу для многих ситуаций, это дает мне только то, что я хочу, без подъема тяжестей.

Ура.


Я обычно использую Threadpool, когда мне нужно просто сделать что-то в другом потоке, и мне все равно, когда он работает или заканчивается. Что-то вроде ведения журнала или, возможно, даже фоновой загрузки файла (хотя есть лучшие способы сделать это в асинхронном стиле). Я использую свой собственный поток, когда мне нужно больше контроля. Также то, что я нашел, - это использование Threadsafe queue (hack your own) для хранения "командных объектов", приятно, когда у меня есть несколько команд, над которыми мне нужно работать в потоке >1. Так ты может разделиться в Xml-файл и поместите каждый элемент в очередь, а затем несколько потоков, работающих над выполнением некоторой обработки этих элементов. Я написал такую очередь еще в uni (VB.net!) что я преобразовал в C#. Я включил его ниже без особой причины (этот код может содержать некоторые ошибки).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        public void EnqueueSafe(T item) {
            lock ( this ) {
                _queue.Enqueue(item);
                if ( _queue.Count >= 1 )
                    Monitor.Pulse(this);
            }
        }

        public T DequeueSafe() {
            lock ( this ) {
                while ( _queue.Count <= 0 )
                    Monitor.Wait(this);

                return this.DeEnqueueUnblock();

            }
        }

        private T DeEnqueueUnblock() {
            return _queue.Dequeue();
        }
    }
}

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

Что касается исходного вопроса, пул потоков полезен для разбиение повторяющихся вычислений на части, которые могут выполняться параллельно (при условии, что они могут выполняться параллельно без изменения результата). Ручное управление потоками полезно для таких задач, как UI и IO.