Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

Вот краткое описание пула потоков в .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Использование пула потоков может иметь незначительные последствия - некоторые .СЕТЕВЫЕ таймеры используют потоки пула потоков и, например, не запускаются.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Проверьте это страница в MSDN:http://msdn.microsoft.com/en-us/library/3dasc8as (ПРОТИВ 80).aspx

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

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

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

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

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

Ваше здоровье.

Обычно я использую Threadpool всякий раз, когда мне нужно просто что-то сделать в другом потоке, и на самом деле мне все равно, когда он запускается или заканчивается.Что-то вроде ведения журнала или, возможно, даже фоновой загрузки файла (хотя есть лучшие способы сделать это в асинхронном стиле).Я использую свой собственный поток, когда мне нужно больше контроля.Также я обнаружил, что использование потокобезопасной очереди (взломайте ее самостоятельно) для хранения "командных объектов" приятно, когда у меня есть несколько команд, над которыми мне нужно работать в > 1 потоке.Таким образом, вы могли бы разделить Xml-файл и поместить каждый элемент в очередь, а затем заставить несколько потоков работать над выполнением некоторой обработки этих элементов.Я писал такие очереди обратном пути в универ (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 вон там.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top