Потоки и работа с ними
Twitter LinkedIn Facebook Адрес электронной почты
- Статья
- Чтение занимает 2 мин
Многопоточность позволяет увеличивать скорость реагирования приложения и, если приложение работает в многопроцессорной или многоядерной системе, его пропускную способность.
Процессы и потоки
Процесс — это исполнение программы. Операционная система использует процессы для разделения исполняемых приложений. Поток — это основная единица, которой операционная система выделяет время процессора. Каждый поток имеет приоритет планирования и набор структур, в которых система сохраняет контекст потока, когда выполнение потока приостановлено. Контекст потока содержит все сведения, позволяющие потоку безболезненно возобновить выполнение, в том числе набор регистров процессора и стек потока. Несколько потоков могут выполняться в контексте процесса. Все потоки процесса используют общий диапазон виртуальных адресов. Поток может исполнять любую часть программного кода, включая части, выполняемые в данный момент другим потоком.
Примечание
Платформа .NET Framework предоставляет способ изоляции приложений в процессе с помощью доменов приложений. (Домены приложений недоступны в .NET Core.) Дополнительные сведения см. в разделе Домены приложений и потоки в статье Домены приложений.
По умолчанию программа .NET запускается с одним потоком, часто называемым основным потоком. Тем не менее она может создавать дополнительные потоки для выполнения кода параллельно или одновременно с основным потоком. Эти потоки часто называются рабочими потоками.
Цели применения нескольких потоков
Используйте несколько потоков, чтобы увеличить скорость реагирования приложения и воспользоваться преимуществами многопроцессорной или многоядерной системы, чтобы увеличить пропускную способность приложения.
Представьте себе классическое приложение, в котором основной поток отвечает за элементы пользовательского интерфейса и реагирует на действия пользователя. Используйте рабочие потоки для выполнения длительных операций, которые, в противном случае будут занимать основной поток, в результате чего пользовательский интерфейс будет недоступен. Для более оперативной реакции на входящие сообщения или события также можно использовать выделенный поток связи с сетью или устройством.
Если программа выполняет операции, которые могут выполняться параллельно, можно уменьшить общее время выполнения путем выполнения этих операций в отдельных потоках и запуска программы в многопроцессорной или многоядерной системе. В такой системе использование многопоточности может увеличить пропускную способность, а также повысить скорость реагирования.
Как использовать многопоточность в .NET
Начиная с .NET Framework 4, для многопоточности рекомендуется использовать библиотеку параллельных задач (TPL) и Parallel LINQ (PLINQ). Дополнительные сведения см. в разделе Параллельное программирование.
Библиотека параллельных задач и PLINQ полагаются на потоки ThreadPool. Класс System.Threading.ThreadPool предоставляет приложения .NET с пулом рабочих потоков. Также можно использовать потоки из пула потоков. Дополнительные сведения см. в разделе Управляемый пул потоков.
Наконец, можно использовать класс System.Threading.Thread, который представляет управляемый поток. Дополнительные сведения см. в разделе Использование потоков и работа с потоками.
Несколько потоков могут требовать доступ к общему ресурсу. Чтобы сохранить ресурс в неповрежденном состоянии и избежать состояния гонки, необходимо синхронизировать доступ к нему потоков. Вы также можете координировать взаимодействие нескольких потоков. Платформа .NET предоставляет ряд типов для синхронизации доступа к общему ресурсу или координации взаимодействия потоков. Дополнительные сведения см. в разделе Обзор примитивов синхронизации.
Исключения следует обрабатывать в потоках. Необработанные исключения в потоках, как правило, приводят к завершению процесса. Дополнительные сведения см. в статье Исключения в управляемых потоках.
См. также
- Объекты и функциональные возможности работы с потоками
- Рекомендации по работе с потоками
- Процессы и потоки
- Параллельная обработка в .NET
- Асинхронные шаблоны программирования в .NET
Видео курс C# базовый (ООП).
Потоки (threads) – курсы ITVDN- Главная >
- Каталог >
- C# базовый (ООП) >
- Потоки (threads) в C#
Для прохождения теста нужно авторизироваться
Войти Регистрация
×
Вы открыли доступ к тесту! Пройти тест
Войдите или зарегестрируйтесь для того чтоб продолжить просмотр бесплатного видео
Войти Регистрация
№1
Введение в ООП, классы и объекты
3:06:55
Материалы урокаДомашние заданияТестирование
Первый видео урок содержит массу полезной информации: автор в течение 3-х часов подробно и максимально доходчиво рассказывает про классы, их поля, свойства и экземпляры, а также раскрывает тему объектно-ориентированного программирования и демонстрирует реализацию одного из ее главных принципов — инкапсуляцию. Весь материал подкрепляется понятными практическими примерами.
Изучив содержание данного урока, вы сможете:
- понимать суть основных парадигм ООП;
- понимать, как устроены и для чего предназначены классы;
- создавать классы и правильно их использовать;
- понимать назначение полей и свойств, уметь их реализовывать;
- создавать конструктор класса;
- понимать принцип инкапсуляции и реализовывать его на языке C#.
Читать дальше…
Классы и объекты. Диаграммы классов.
1:17:25
Материалы урокаДомашние заданияТестирование
В видео уроке «Классы и объекты. Диаграммы классов» будет продолжена тема урока «Введение в OOП. Классы и объекты», а также будет раскрыта тема возможности языка программирования C# разделять определение класcа между двумя и/или более файлами, именуемая частичными или partial классами. После ознакомления с частичными классами в С#, будут рассмотрены диаграммы классов, связи отношений между классами такие как ассоциация, агрегация, композиция, реализация, самоассоциация зависимости и другие.
Читать дальше…
Наследование и полиморфизм в C#
2:00:10
Материалы урокаДомашние заданияТестирование
Все языки ООП, включая С#, основаны на трёх парадигмах (концепциях), называемых инкапсуляцией, наследованием и полиморфизмом. В ходе видео урока Вам будет представлена информация о двух основных парадигмах ООП — полиморфизме и наследовании, также Вы познакомитесь с модификаторами доступа и виртуальными членами. В ходе урока, на примерах, представленных тренером, Вы сможете понять практическое применение полиморфизма и наследования и научитесь работать с иерархией классов.
Читать дальше…
Абстракция. Абстрактные классы и интерфейсы
1:57:42
Материалы урокаДомашние заданияТестирование
В видео уроке будет продемонстрированы практические примеры создания и использования абстрактных классов и интерфейсов. Абстракция позволяет программисту рассматривать объект, не разбирая сумму сложных частей, из которых состоит данный объект. В ходе видео урока Вы получите необходимые знания, которые помогут Вам разобраться с понятием абстракции в С#, а также научитесь создавать и реализовывать свои собственные абстрактные классы и интерфейсы и понимать разницу между ними.
Читать дальше…
Массивы и индексаторы
1:07:45
Материалы урокаДомашние заданияТестирование
В видео уроке рассмотрены примеры создания и практического применения массивов и индексаторов. Тренер объясняет Вам принципы создания и практического применения индексаторов и способы их переопределения. Программисты с опытом знакомы с процессом обращения к индивидуальным элементам, которые содержат стандартные массивы. В видео уроке будет представлена возможность языка программирования С# проектировать специальные классы, которые можно индексировать подобно стандартному массиву через определение индексатора. Индексаторы часто используют при создании специальных типов — коллекций. В этом уроке Вы подробно изучите возможности индексаторов в языке C#.
Читать дальше…
Статические и вложенные классы
1:39:37
Материалы урокаДомашние заданияТестирование
В видео уроке будут рассмотрены статические классы, принципы создания и практического применения статических членов. Также, в ходе видео урока будет объяснена работа и использование расширяющих методов.Во второй части видео урока тренер рассмотрит понятие вложенных классов и шаблон проектирования «Одиночка» (Singleton).
Читать дальше…
Структуры и их разновидности
1:30:45
Материалы урокаДомашние заданияТестирование
В видео уроке будет представлена полная информация о структурах, рассмотрены отличия между классами и структурами, а также рассказаны практические советы по их применению. Структуры — фундаментальные типы данных в языке программирования C#. Структуры по своей сути просты и зачастую начинающие программисты могут не понимать, насколько быстро работа со структурами может стать сложной.
Читать дальше…
Перечисления (enum)
1:41:02
Материалы урокаДомашние заданияТестирование
В данном видео уроке будут рассмотрены такие понятия как упаковка (boxing) и распаковка (unboxing), структурный тип DateTime, а также работа с перечислениями(enum). В ходе занятия тренер ознакомит студентов с практическими примерами, которые позволят с легкостью использовать и применять полученные на уроке знания. Последней темой, рассмотренной в видео уроке будут перечисления, которые предоставляют способ эффективно определить набор именованных интегральных констант.
Читать дальше…
Делегаты
1:59:15
Материалы урокаДомашние заданияТестирование
Видео урок позволяет студенту понять работу лямбда-выражений и делегатов. Начинающие программисты, продумывая интерфейс очередного класса, часто понимают, что очень полезной могла бы быть возможность передавать в качестве аргумента методов часть исполняемого кода. Делегаты позволяют Вам реализовать подобный подход. В видео уроке будет представлена информация о создании и применении делегатов, а также основные трудности при работе с ними.
Читать дальше…
Универсальные шаблоны (generics) в C#
1:40:03
Материалы урокаДомашние заданияТестирование
В языке программирования C# существует два механизма для создания кода, который будет повторно использован через различные типы — уже рассмотренное ранее наследование и обобщения. Обобщения, в отличии от наследования, выражают повторное использование кода через использование универсальных шаблонов(generics), в которых применяются различные типы данных на этапе выполнения. В ходе видео урока тренер рассмотрит с Вами все основы работы с обобщениями и их применение в языке программирования C#, а также расскажет о контрвариантности и ковариантности.
Читать дальше…
Ограничения универсальных шаблонов в C#
0:57:09
Материалы урокаДомашние заданияТестирование
В видео уроке «Ограничения универсальных шаблонов» Вас ждет продолжение знакомства с универсальными шаблонами в C#. Вы узнаете, каким образом можно использовать ограничения для обобщенных типов данных. В ходе видео урока тренер остановит Ваше внимание на работе с Nullable типами, а также операциях поглощения, показав примеры практического их использования.
Читать дальше…
События (events) в C#
1:41:29
Материалы урокаДомашние заданияТестирование
Весь видео урок будет всецело посвящен работе с событиями в C#. В деталях будет рассмотрено, каким образом создавать «издателей» и «подписчиков», а также обращаться к созданным событиям и вызывать их. Тренер уделит отдельное внимание делегату EventHandler и базовому классу EventArgs, а также работе с ними.
Читать дальше…
№13
Потоки (threads) в C#
2:16:40
Материалы урокаДомашние заданияТестирование
В процессе просмотра видео урока Вы получите основные сведения, которые потребуются Вам для работы с многопоточностью в языке программирования C#. Многопоточность — важное средство многозадачного программирования среды .NET. Видео урок даст Вам основное понимание многопоточности в языке программирования С#. Также в ходе урока тренер расскажет Вам об использовании делегатов ThreadStart и ParameterizedThreadStart и объяснит работу с критическими секциями, как средствами синхронизации доступа потоков к различным разделяемым ресурсам.
Читать дальше…
Коллекции в C#
2:02:27
Материалы урокаДомашние заданияТестирование
В видео уроке будут объяснены коллекции, их назначение и примеры их практического применения. Также Вы детально изучите базовые интерфейсы IEnumerable, IEnumerator. Также в ходе видео урока тренер рассмотрит с Вами примеры создания и использования пользовательских коллекций, продемонстрирует наглядные примеры по работе оператора yield.
Читать дальше…
Обработка исключений (exception)
1:36:58
Материалы урокаДомашние заданияТестирование
В этом видео уроке Вы узнаете какие системные исключения существуют в языке C# и как правильно обрабатывать исключительные ситуации с помощью конструкции try — catch — finally. Также вы научитесь создавать свои объекты исключения. При выполнение приложения может сложится ситуация, когда корректное выполнение приложения невозможно. Например, приложение читает файл на диске, которого нет. В такой ситуации в приложении возникает специальный объект – исключение. Исключение описывает проблему, которая возникла в момент выполнения кода и позволяет разработчику выбрать подходящее действие для решения проблемы.
Читать дальше…
Перегрузка операторов в C#
1:41:51
Материалы урокаДомашние заданияТестирование
В данном видео уроке тренером будет рассмотрен базовый класс object его применение и использование, а так же техника перегрузки операторов. В процессе объяснения будет затронута техника клонирования, а также будет рассмотрено назначение шаблона проектирования «Прототип» (Prototype) и интерфейса ICloneable. Вам будут продемонстрировано практическое использование техники перегрузки операторов.
Читать дальше. ..
Анонимные и динамические типы. LINQ.
1:53:42
Материалы урокаДомашние заданияТестирование
Данный видео урок будет очень важен для понимания всех современных технологий. В ходе видео урока тренер познакомит Вас с основами LINQ, а также с анонимными и динамическими типами, которые активно используются при построении запросов. Язык LINQ — это набор функций, который позволяет значительно расширить возможности синтаксиса языка программирования C#. Language Integrated Query (LINQ) представляет расширение языка C#, которое дает возможность работать с различными источниками данных используя синтаксис запросов подобный языку SQL.
Читать дальше…
Пространства имен. Директивы препроцессора в C#
1:15:05
Материалы урокаДомашние заданияТестирование
В этом видеоуроке Вы узнаете, что такое пространства имен и как правильно организовывать проект используя пространства имен. Также Вы узнаете, как создавать библиотеки (DLL) в языке C#. Тренер рассмотрит тип проекта Class Library и на простом примере объяснить для чего используются библиотеки. В конце урока Вы изучите новые модификаторы доступа internal и internal protected и рассмотрите некоторые препроцессорные директивы, узнаете, как они могут помочь при разработке больших решений.
Читать дальше…
ПОКАЗАТЬ ВСЕ
основные темы, рассматриваемые на уроке
0:00:30
Многозадачность
0:15:25
Пространство имен System.Threading
0:19:25
Работа с потоками
1:01:30
Использование одного метода в двух потоках
1:15:00
Передача данных в поток
1:22:30
Анонимные методы
1:28:50
Основные и фоновые потоки
1:37:10
Техника синхронизации доступа к ресурсу
1:42:10
Критическая секция
ПОКАЗАТЬ ВСЕ
Рекомендуемая литература
Язык программирования C# 5.0 и платформа .NET 4.5 Эндрю Троелсен
Титры видеоурока
Титров к данному уроку не предусмотрено
ПОДРОБНЕЕ
ПОДРОБНЕЕ
ПОДРОБНЕЕ
ПОДРОБНЕЕ
ПОДРОБНЕЕ
ПОДРОБНЕЕ
ПОДРОБНЕЕ
Использование нитей и потоков | Microsoft Узнайте
Редактировать
Твиттер LinkedIn Фейсбук Эл. адрес
- Статья
- 3 минуты на чтение
С помощью .NET вы можете писать приложения, выполняющие несколько операций одновременно. Операции, которые потенциально могут задерживать другие операции, могут выполняться в отдельных потоках, процесс, известный как многопоточность или свободная многопоточность .
Приложения, использующие многопоточность, лучше реагируют на ввод данных пользователем, поскольку пользовательский интерфейс остается активным, поскольку задачи, интенсивно использующие процессор, выполняются в отдельных потоках. Многопоточность также полезна при создании масштабируемых приложений, поскольку вы можете добавлять потоки по мере увеличения рабочей нагрузки.
Примечание
Если вам нужен больший контроль над поведением потоков приложения, вы можете управлять потоками самостоятельно.
Как создать и запустить новый поток
Вы создаете новый поток, создавая новый экземпляр класса System.Threading.Thread. Вы предоставляете конструктору имя метода, который хотите выполнить в новом потоке. Чтобы запустить созданный поток, вызовите метод Thread.Start. Дополнительные сведения и примеры см. в статье Создание потоков и передача данных во время запуска и в справочнике API потоков.
Как: Остановить поток
Чтобы прекратить выполнение потока, используйте System.Threading.CancellationToken. Он предоставляет унифицированный способ совместной остановки потоков. Дополнительные сведения см. в разделе Отмена в управляемых потоках.
Иногда невозможно совместно остановить поток, так как он выполняет сторонний код, не предназначенный для совместной отмены. В этом случае вы можете захотеть принудительно прекратить его выполнение. Чтобы принудительно завершить выполнение потока, в .NET Framework вы можете использовать метод Thread.Abort. Этот метод вызывает исключение ThreadAbortException в потоке, для которого он был вызван. Дополнительные сведения см. в разделе Уничтожение потоков. Метод Thread.Abort не поддерживается в .NET Core. Если вам нужно принудительно прекратить выполнение стороннего кода в .NET Core, запустите его в отдельном процессе и используйте метод Process.Kill.
System.Threading.CancellationToken недоступен до .NET Framework 4. Чтобы остановить поток в более старых версиях .NET Framework, используйте методы синхронизации потоков для реализации совместной отмены вручную. Например, вы можете создать переменное логическое поле shouldStop
и использовать его для запроса остановки кода, выполняемого потоком. Дополнительные сведения см. в разделе volatile в справочнике по C# и System.Threading.Volatile.
Используйте метод Thread.Join, чтобы заставить вызывающий поток ожидать завершения остановленного потока.
Как: приостановить или прервать поток
Метод Thread.Sleep используется для приостановки текущего потока на указанное время. Вы можете прервать заблокированный поток, вызвав метод Thread.Interrupt. Дополнительные сведения см. в разделе Приостановка и прерывание потоков.
Свойства резьбы
В следующей таблице представлены некоторые свойства резьбы:
Свойство | Описание |
---|---|
Жив | Возвращает true , если поток был запущен, но еще не завершился нормально или не был прерван. |
Исфон | Получает или задает логическое значение, указывающее, является ли поток фоновым. Фоновые потоки похожи на потоки переднего плана. Однако фоновый поток не предотвращает остановку процесса. Как только все приоритетные потоки, принадлежащие процессу, останавливаются, среда CLR завершает процесс, вызывая метод Abort для фоновых потоков, которые все еще активны. Дополнительные сведения см. в разделе Потоки переднего и заднего плана. |
Имя | Получает или задает имя потока. Чаще всего используется для обнаружения отдельных потоков при отладке. |
Приоритет | Получает или задает значение ThreadPriority, используемое операционной системой для определения приоритетов планирования потоков. Дополнительные сведения см. в разделе Планирование потоков и справочник по ThreadPriority. |
ThreadState | Получает значение ThreadState, содержащее текущие состояния потока. |
См. также
- System.Threading.Thread
- Резьба и нарезание резьбы
- Параллельное программирование
Обратная связь
Просмотреть все отзывы о странице
.
NET ThreadPool голодает, и как это усугубляется очередями | Кевин Госсе | Блог Criteo по исследованиям и разработкамБыло много разговоров о голодании ThreadPool в .NET:
- https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with- perfview-why-my-service-is-not-saturating-all-cores-or-seems-to-stall/
- https://blogs.msdn.microsoft.com/vsoservice/?p=17665
- Или даже в нашем собственном блоге: http://labs.criteo.com/2018/09/monitor-finalizers-contention-and-threads-in-your-application/
О чем он? Это один из многочисленных способов, с помощью которых асинхронный код может сломаться, если вы ожидаете выполнения задачи синхронно.
Photo by Tim Gouw на UnsplashЧтобы проиллюстрировать это, рассмотрим веб-сервер, который будет выполнять этот код:
Вы запускаете асинхронную операцию ( DoSomethingAsync
), а затем блокируете текущий поток. В какой-то момент асинхронной операции потребуется поток для завершения выполнения, поэтому он запросит новый поток у ThreadPool. В итоге вы используете два потока для операции, которую можно было бы выполнить всего одним: один активно ожидает вызова метода Wait(), а другой выполняет продолжение. В большинстве случаев это нормально. Но это может стать проблемой, если вы имеете дело с потоком запросов:
- Запрос 1 поступает на сервер.
ProcessRequest
вызывается из потока ThreadPool. Он запускает асинхронную операцию, затем ожидает ее - Запросы 2, 3, 4 и 5 поступают на сервер
- Асинхронная операция завершается, и ее продолжение ставится в очередь в ThreadPool
- Тем временем, поскольку поступило 4 запроса, 4 вызова
ProcessRequest
были поставлены в очередь перед вашим продолжением - Каждый из этих запросов, в свою очередь, запускает асинхронную операцию и блокирует поток ThreadPool
В сочетании с тем фактом, что ThreadPool растет очень медленно (один поток в секунду или около того), легко понять, как всплеск запросов может подтолкнуть систему к ситуации голодания потоков. Но на картинке чего-то не хватает: в то время как всплеск может временно заблокировать систему, если рабочая нагрузка не будет постоянно увеличиваться, ThreadPool должен быть способен расти достаточно, чтобы в конечном итоге восстановиться.
Однако это не соответствует тому, что мы наблюдали на наших собственных серверах. Обычно мы перезапускаем наши экземпляры, как только происходит голодание, но в одном случае мы этого не сделали. ThreadPool вырос до жестко заданного предела (32767 потоков), и система так и не восстановилась:
Если посчитать, 32767 потоков должно быть более чем достаточно для обработки 1000–2000 запросов в секунду, которые обрабатывают наши серверы, даже если для каждого запроса требуется 10 потоков!
Кажется, что-то еще происходит.
Рассмотрим следующий код. Потратьте минуту, чтобы угадать, что произойдет:
Производитель ставит в очередь 5 вызовов Process каждую секунду. В Process мы уступаем, чтобы избежать блокировки вызывающего абонента, затем запускаем задачу, которая будет ждать 1 секунду и ждать ее. Всего мы запускаем 5 задач в секунду, и для каждой из этих задач потребуется дополнительная задача. Итак, нам нужно 10 потоков, чтобы поглощать постоянную нагрузку. ThreadPool вручную настроен на запуск с 8 потоков, поэтому нам не хватает 2 потоков. Я ожидаю, что программа будет бороться в течение 2 секунд, пока ThreadPool не вырастет, чтобы поглотить рабочую нагрузку. Затем ему нужно немного увеличиться, чтобы обработать дополнительные рабочие элементы, которые мы поставили в очередь в течение 2 секунд. Через несколько секунд ситуация стабилизируется.
Но если вы запустите программу, вы увидите, что ей удалось несколько раз отобразить «Завершено» в консоли, после чего больше ничего не происходит:
Обратите внимание, что этот код предполагает, что Environment.ProcessorCount
меньше или равно 8 на вашем компьютере. Если он больше, то ThreadPool запустится с большим количеством доступных потоков, и вам нужно уменьшить задержку Thread. Sleep
в Producer()
, чтобы задать те же условия.
Глядя на диспетчер задач, мы видим, что загрузка ЦП равна 0, а количество потоков растет примерно на один в секунду:
Здесь я дал ему поработать некоторое время и получил колоссальные 989 потоков, пока еще ничего не происходит! Несмотря на то, что 10 потоков должно быть достаточно для обработки рабочей нагрузки. Так, что происходит?
В этом коде важен каждый бит. Например, если мы удалим Task.Yield
и вместо этого вручную запустим новые задачи через Producer
(комментарии указывают на изменения):
Тогда мы получаем предсказанное поведение! Сначала приложение немного борется, пока ThreadPool не вырастет достаточно. Тогда у нас есть постоянный поток сообщений, и количество потоков стабильно (в моем случае 29).
Что, если мы возьмем этот рабочий код, но запустим Producer
в отдельном потоке?
Это освобождает один поток из ThreadPool, поэтому следует ожидать, что он будет работать немного лучше. Тем не менее, мы получаем первый случай: приложение выводит несколько сообщений перед зависанием, и количество потоков растет до бесконечности.
Вернем Producer
в поток ThreadPool, но используем флаг PreferFairness
при запуске Process
задач: потоков увеличивается до бесконечности.
Так что же происходит на самом деле?
Чтобы понять, что происходит, нам нужно покопаться во внутренностях ThreadPool. Точнее, в том, как рабочие элементы ставятся в очередь.
Существует несколько статей, объясняющих, как работает очередь ThreadPool (http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx). Короче говоря, важной частью является то, что ThreadPool имеет несколько очередей. Для N потоков в ThreadPool существует N+1 очередей: одна локальная очередь для каждого потока и одна глобальная очередь. Правила выбора очереди для вашего предмета просты:
Элемент будет помещен в глобальную очередь:
- Если поток, который включает в себя элемент, не является потоком потока
- , если он использует
ThreadPool. QueueUserWorkitem
илиThreadPool.unsafequeueueuserworkitem
- , если он использует
asfode.factory.startnew
- , если он использует
. PreferFairness
флаг - Если он использует
Task.Yield
в планировщике задач по умолчанию
Почти во всех других случаях элемент будет поставлен в очередь в локальную очередь потока.
Как элементы удаляются из очереди? Всякий раз, когда поток ThreadPool освобождается, он начинает просматривать свою локальную очередь и удалять элементы из очереди в порядке LIFO. Если локальная очередь пуста, поток просматривает глобальную очередь и удаляется из очереди в порядке FIFO. Если глобальная очередь также пуста, то поток просматривает локальные очереди других потоков и удаляется из очереди в порядке FIFO (чтобы уменьшить конкуренцию с владельцем очереди, который удаляется из очереди в порядке LIFO).
Как это повлияет на нас? Вернемся к нашему ошибочному коду.
Во всех вариантах кода Thread.Sleep(1000)
ставится в очередь в локальной очереди, поскольку Process
всегда выполняется в потоке ThreadPool. Но в некоторых случаях мы ставим Process
в глобальную очередь, а в других — в локальные очереди:
- вторая версия, мы используем
Task.Factory.StartNew
, который ставит в локальную очередь - В третьей версии мы меняем поток
Producer
, чтобы он не использовал ThreadPool, поэтомуTask.Factory.StartNew
ставится в очередь в глобальную очередь - В четвертой версии
Producer
снова является потоком ThreadPool но мы используемTaskCreationOptions.PreferFairness
при постановке в очередьProcess
, таким образом снова используя глобальную очередь
Мы видим, что единственная работающая версия была той, которая не использовала глобальную очередь. Оттуда нужно просто соединить точки:
- Исходное состояние: мы помещаем нашу систему в состояние, когда ThreadPool голодает (т. е. все потоки заняты)
- Мы помещаем в глобальную очередь 5 элементов в секунду
- Каждый из этих элементов при выполнении ставит в очередь другой элемент в локальную очередь и ожидает его
- Когда новый поток порождается ThreadPool, этот поток сначала просматривает свою собственную локальную очередь, которая пуста (поскольку она только что родилась). Затем он выберет элемент из глобальной очереди
- Поскольку мы помещаем в глобальную очередь быстрее, чем растет ThreadPool (5 элементов в секунду против 1 потока в секунду), восстановить систему совершенно невозможно. Из-за приоритета, вызванного использованием глобальной очереди, чем больше потоков мы добавляем, тем больше нагрузка на систему
При использовании вместо этого локальной очереди (вторая версия кода) новые потоки будут выбирать элементы из локальных очередей других потоков, поскольку глобальная очередь пуста. Следовательно, новых нитей помогают снизить нагрузку на систему.
Как это отразится на реальном сценарии?
Возьмем случай службы на основе HTTP. Стек HTTP, независимо от того, использует ли он Windows http.sys или другой API, скорее всего, является родным. Когда он перенаправляет новые запросы пользовательскому коду .NET, он ставит их в очередь в ThreadPool. Эти элементы обязательно попадут в глобальную очередь, поскольку собственный стек HTTP не может использовать потоки .NET ThreadPool. Затем пользовательский код полагается на async/await и, скорее всего, полностью использует локальные очереди. Это означает, что в ситуации голодания новые потоки, порожденные ThreadPool, будут обрабатывать новые запросы (поставленные в глобальную очередь собственным кодом), а не завершать те, которые уже находятся в канале (поставленные в очередь в локальных очередях). Следовательно, мы оказываемся в описанной выше ситуации, когда каждый новый поток увеличивает нагрузку на систему.
Еще одна ситуация, когда все может обернуться ужасно, — это если код блокировки выполняется как часть обратного вызова таймера. Обратные вызовы таймера помещаются в глобальную очередь. Я полагаю, что такой случай можно найти здесь (обратите особое внимание на вызов TimerQueueTimer.Fire
в начале стека вызовов для показанных потоков 1202): https://blogs.msdn.microsoft.com/vsoservice/?p =17665.
С точки зрения пользовательского кода, к сожалению, не очень. Конечно, в идеальном мире мы бы использовали неблокирующий код и никогда не оказались бы в ситуации голодания ThreadPool. Использование выделенного пула потоков вокруг блокирующих вызовов может очень помочь, так как вы перестанете конкурировать с глобальной очередью за новые потоки. Наличие системы обратного давления также является хорошей идеей. В Criteo мы экспериментируем с системой обратного давления, которая измеряет, сколько времени требуется ThreadPool для удаления элемента из локальной очереди. Если это занимает больше нескольких настроенных пороговых значений, то мы прекращаем обработку входящих запросов до тех пор, пока система не восстановится. Пока он показывает многообещающие результаты.
Я считаю, что с точки зрения BCL мы должны рассматривать глобальную очередь как еще одну локальную очередь. Я действительно не вижу причин, по которым его следует рассматривать в приоритете по сравнению со всеми другими локальными очередями. Если мы опасаемся, что глобальная очередь будет расти быстрее, чем другие очереди, мы можем придать вес случайному выбору очереди. Это, вероятно, потребует некоторых корректировок, но это стоит изучить.
Дайте нам знать, что вы думаете, или ознакомьтесь с другими статьями Кевина?
Возврат к потоку пользовательского интерфейса в WPF/UWP, в современном C#
Использование асинхронного механизма для прозрачного переключения на поток пользовательского интерфейса при необходимости
Вдохновение для дней карантина: как подключить Raspberry Pi к тренировочному велосипеду, чтобы транслировать скорость и приостанавливать видео…
medium.