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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

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

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

Ответ 12

Не забудьте изучить работника фона.

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

Приветствия.

Ответ 13

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

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

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

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

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

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

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

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

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

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

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

Результаты тестирования

Ответ 14

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

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();
        }
    }
}

Ответ 15

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

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