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

127

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

участник
источник

Ответы:

47

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

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

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

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

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

Роберт Гулд
источник
2
Хорошо, но можете ли вы объяснить, почему вы так подходите к этому? Например, в чем обратная сторона использования пула потоков для загрузки с удаленных серверов или выполнения операций ввода-вывода на диск?
8
Если поток ожидает объекта синхронизации (событие, семафор, мьютекс и т. Д.), То поток не использует ЦП.
Браннон
7
Как сказал Браннон, распространенным мифом является то, что создание нескольких потоков влияет на производительность. На самом деле неиспользуемые потоки потребляют очень мало ресурсов. Переключение контекста начинает быть проблемой только на серверах с очень высокими требованиями (в этом случае см. Альтернативу портам завершения ввода-вывода).
FDCastel
12
Влияют ли простаивающие потоки на производительность? Это зависит от того, как они ждут. Если они хорошо написаны и ожидают объекта синхронизации, они не должны потреблять ресурсы ЦП. Если ожидание в цикле, который периодически просыпается для проверки результатов, значит, он тратит ресурсы ЦП. Как всегда, все сводится к хорошему кодированию.
Билл,
2
Неактивные управляемые потоки съедают память для своего стека. По умолчанию 1 МБ на поток. Так что лучше, чтобы все потоки работали.
Вадим Стецяк
48

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

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

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

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

paxdiablo
источник
28

Вот хороший обзор пула потоков в .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

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

Франси Пенов
источник
8
-1 за ссылку. Я уверен, что это хорошая ссылка, но я ожидаю, что SO будет самодостаточной.
Джон Дэвис
26
@ stimpy77 - тогда это неправильное ожидание. SO никогда не может быть самодостаточным, потому что он не является высшим авторитетом по всем вопросам, и вся подробная информация по каждой теме не может (и должна) дублироваться в каждом и каждом ответе SO, касающемся этой темы. (и я не думаю, что у вас даже достаточно репутации, чтобы отрицать каждый ответ одного только Джона Скита, который имеет исходящую ссылку, не говоря уже обо всех ответах всех пользователей SO, у которых есть исходящие ссылки :-))
Фрэнси Пенов,
2
Возможно, я был чересчур краток, возможно, мне следует уточнить. Я не против ссылок. Я против ответов, содержащих только ссылку. Я не думаю, что это ответ. Теперь, если бы было опубликовано краткое изложение ответа, чтобы резюмировать, как применяется связанный контент, это было бы приемлемо. Кроме того, я пришел сюда в поисках ответа на ту же проблему, и этот ответ меня раздражал, потому что это была еще одна ссылка, по которой я должен был щелкнуть, чтобы иметь представление о том, что она может сказать по поводу конкретной проблемы. В любом случае, какое отношение к этому имеет Джон Скит? И какое мне дело?
Джон Дэвис,
8
«Вы пришли на этот пост через два года после того, как он был размещен, и все, что я здесь скопировал, могло быть уже устаревшим». Так могла бы ссылка. Опубликуйте краткое, но полное резюме при публикации ссылки, вы никогда не узнаете, устарела ли ссылка или нет.
Джон Дэвис,
2
Я не согласен с stimpy: не с идеей постов, содержащих тонны информации из-за невозможности реализации, ни с кем-то кричать по этому поводу. Я бы сказал, что более вероятно, что ссылка станет неработоспособной, чем контент станет устаревшим / устраненным. Так что больше контента - это хорошо, когда позволяет случай. Мы все (в основном) добровольцы, так что будьте благодарны за то, что вы получили - спасибо,
Фрэнси
14

Я настоятельно рекомендую прочитать эту бесплатную электронную книгу: Threading in C # Джозефа Альбахари

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

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

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

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

jrupe
источник
8

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

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

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

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

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

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

Brannon
источник
1
ThreadPool - это умный автомат. «Если его очередь остается неподвижной более полсекунды, она отвечает, создавая больше потоков - по одному каждые полсекунды - в соответствии с емкостью пула потоков» ( albahari.com/threading/#_Optimizing_the_Thread_Pool ). Также почти асинхронные операции с BeginXXX-EndXXX используются через ThreadPool. Поэтому использование ThreadPool для загрузки данных является нормальным и часто используется неявно.
Artru
6

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

mancaus
источник
2
Мы обнаружили это на собственном горьком опыте! ASP.Net использует Threadpool, поэтому мы не могли использовать его так агрессивно, как хотелось бы.
noocyte
3

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

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

Томас Братт
источник
2

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

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

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

Билл
источник
Пожалуйста, поясните, имеете ли вы в виду «пул потоков» или «пул потоков». Это очень разные вещи (по крайней мере, в MS CLR).
bzlm 01
2

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

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

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

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

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

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

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

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

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

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

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

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


источник
1
К вашему сведению, забыл упомянуть, что задачи и потоки .NET имитируют параллелизм в .NET, а управление выполняется в .NET, а не в ОС - последняя гораздо более эффективна при управлении параллельным выполнением. Я использую задачи для многих вещей, но я использую поток ОС для высокой производительности. MS утверждает, что задачи и потоки .NET лучше, но в целом они предназначены для балансировки параллелизма между приложениями .NET. Однако серверное приложение будет работать лучше, если ОС будет обрабатывать параллелизм.
Хотелось бы увидеть реализацию вашего собственного Threadpool. Приятно написать!
Фрэнсис
Я не понимаю ваших результатов теста. Что означает "Units Ran"? Вы сравниваете 34 такса с 512 потоками? Не могли бы вы объяснить это?
Elmue
Модуль - это просто метод для одновременного выполнения в рабочем потоке Task, Thread или .NET ThreadPool, мой тест сравнивает производительность при запуске / запуске. У каждого теста есть 30 секунд для создания 512 потоков с нуля, 512 задач, 512 рабочих потоков ThreadPool или возобновления пула из 512 запущенных потоков, ожидающих выполнения контекста. Рабочие потоки задач и ThreadPool имеют медленное вращение, поэтому 30 секунд недостаточно, чтобы развернуть их все. Однако, если минимальное количество рабочих потоков ThreadPool сначала установлено равным 512, как задачи, так и рабочие потоки ThreadPool будут раскручиваться почти так же быстро, как 512 потоков с нуля.
1

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

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

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

Лайош
источник
Хорошо, я думаю, это связано с моим другим вопросом. Как узнать, сколько потоков доступно в любой момент времени?
Что ж, сложно сказать. Вам нужно будет провести тестирование производительности. После точки добавление большего количества потоков не даст вам большей скорости. Узнайте, сколько процессоров на машине, это будет хорошей отправной точкой. Затем перейдите оттуда, если скорость обработки не улучшится, не добавляйте больше потоков.
Лайош
1

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

JeffFoster
источник
1

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

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

антонио
источник
1

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

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

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

SetiSeeker
источник
когда это простое приложение, которое продолжает работать, а у вас есть еще одна задача, сделать этот код очень легко. вы не предоставили ссылки: спецификация и руководство
zanlok
0

Я обычно использую Threadpool всякий раз, когда мне нужно что-то сделать в другом потоке, и мне все равно, когда он запускается или заканчивается. Что-то вроде ведения журнала или, возможно, даже загрузки файла в фоновом режиме (хотя есть более эффективные способы сделать это в асинхронном стиле). Я использую свой собственный поток, когда мне нужно больше контроля. Кроме того, я обнаружил, что использование очереди Threadsafe (взломайте свою) для хранения «объектов команд» - это хорошо, когда у меня есть несколько команд, над которыми мне нужно работать в> 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();
        }
    }
}
noocyte
источник
Некоторые проблемы с этим подходом: - Вызов DequeueSafe () будет ждать, пока элемент не станет EnqueuedSafe (). Рассмотрите возможность использования одной из перегрузок Monitor.Wait () с указанием тайм-аута. - Блокировка не соответствует передовой практике, вместо этого нужно создать поле объекта только для чтения. - Несмотря на то, что Monitor.Pulse () является легковесным, его вызов, когда очередь содержит только 1 элемент, будет более эффективным. - DeEnqueueUnblock () должен предпочтительно проверять queue.Count> 0. (необходимо, если используются Monitor.PulseAll или тайм-ауты ожидания)
Крейг Николсон
0

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

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

cdiggins
источник