Когда использовать пул потоков в C#?
Я пытался изучить многопоточное программирование на C#, и я смущен, когда лучше всего использовать пул потоков против создания собственных потоков. Одна книга рекомендует использовать пул потоков только для небольших задач (что бы это ни значило), но я не могу найти никаких реальных рекомендаций. Какие соображения вы используете при принятии этого программного решения?
15 ответов:
Если у вас есть много логических задач, которые требуют постоянной обработки, и вы хотите, чтобы это было сделано параллельно использовать пул+планировщик.
Если вам нужно сделать ваши задачи, связанные с 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 quad core с 16 ГБ оперативной памяти), дающие каждому 30 осталось несколько секунд до старта. Выполняемый код выполняет простой файловый ввод-вывод на SSD-диске.
пулы потоков отлично подходят, когда у вас есть больше задач для обработки, чем доступных потоков.
вы можете добавить все задачи в пул потоков и указать максимальное количество потоков, которые могут выполняться в определенное время.
проверить этой страница на MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as(против.80).аспн
всегда используйте пул потоков, если можете, работайте на самом высоком уровне абстракции. Пулы потоков скрывают создание и уничтожение потоков для вас, это обычно хорошо!
большую часть времени вы можете использовать пул, поскольку вы избегаете дорогостоящего процесса создания потока.
однако в некоторых сценариях вы можете создать поток. Например, если вы не единственный, кто использует пул потоков, и создаваемый поток является долгоживущим (чтобы избежать потребления общих ресурсов) или, например, если вы хотите управлять размером стека потока.
Если у вас есть фоновая задача, которая будет жить в течение длительного времени, как и на протяжении всего срока службы вашего приложения, то создание собственного потока является разумной вещью. Если у вас есть короткие задания, которые необходимо выполнить в потоке, используйте пул потоков.
в приложении, где вы создаете много потоков, накладные расходы на создание потоков становятся существенными. Использование пула потоков создает потоки один раз и повторно использует их, что позволяет избежать создания потока накладные расходы.
в приложении, над которым я работал, переход от создания потоков к использованию пула потоков для короткоживущих потоков действительно помог через put приложения.
Не забывайте, что для расследования обстоятельств работника.
Я нахожу для многих ситуаций, это дает мне только то, что я хочу, без подъема тяжестей.
Ура.
Я обычно использую Threadpool всякий раз, когда мне нужно просто сделать что-то в другом потоке, и мне все равно, когда он запускается или заканчивается. Что-то вроде регистрации или, возможно, даже фоновой загрузки файла (хотя есть лучшие способы сделать это в асинхронном стиле). Я использую свой собственный поток, когда мне нужно больше контроля. Также то, что я нашел, использует потокобезопасную очередь (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.
Comments