Thread в .NET
1. Thread в .NET
1.1. Потоки (Threads): Основы
Что такое поток?
Поток (Thread) — это базовая единица выполнения, которой операционная система (ОС) выделяет процессорное время. Если ваш код должен выполняться, ОС назначает для его исполнения поток. Каждое приложение (консольное, веб-API и т.д.) запускается внутри хотя бы одного потока.
"Железная" основа выполнения потоков
Производительность потоков напрямую зависит от характеристик процессора (CPU):
Количество CPU (процессоров): От 1 (обычные ПК) до N (серверы).
Количество ядер на CPU: Каждое физическое ядро способно выполнять один поток инструкций независимо. От 1 до M ядер.
Hyper-Threading (HT) / Simultaneous Multithreading (SMT): Технологии (Intel/AMD), позволяющие одному физическому ядру обрабатывать несколько (обычно два) потоков выполнения ОС (Software Threads) одновременно. ОС видит каждую такую возможность как отдельное логическое ядро (Logical Processor). Например, 4-ядерный CPU с HT покажет ОС 8 логических ядер.
Разделение потоков: Аппаратный и Программный уровни
Часто используется следующее разделение терминов:
Аппаратный поток (Hardware Thread / Logical Processor): Это возможность физического ядра обрабатывать поток инструкций. Каждое логическое ядро (реальное или виртуальное через HT/SMT) представляет собой один конвейер для выполнения инструкций потока ОС.
Поток ОС (OS Thread / Software Thread): Это единица выполнения, управляемая планировщиком ОС. Именно поток ОС назначается на выполнение на логическое ядро. Он содержит:
Код приложения для выполнения.
Контекст (Context): Критически важный набор данных, включающий:
Текущую точку выполнения (адрес следующей инструкции).
Состояние регистров процессора.
Указатель стека потока.
Приоритет и другую служебную информацию ОС.
Тип нагрузки: Код внутри потока ОС можно условно разделить по характеру:
CPU-bound (Вычислительно-ограниченный): Интенсивные вычисления, нагружающие процессор (математические операции, обработка данных).
I/O-bound (Ввод/Вывод-ограниченный): Операции, связанные с ожиданием данных (чтение/запись файлов, сетевые запросы, доступ к БД). Процессор часто простаивает в ожидании.
Зачем нужны HT/SMT? (Эффективность использования ядер)
Ключевая проблема: Когда поток ОС на ядре выполняет I/O-bound операцию, физическое ядро вынуждено простаивать, ожидая данных (микропаузы).
Решение HT/SMT: Одно физическое ядро способно хранить состояние (контекст) нескольких (обычно двух) потоков ОС. Если один поток на таком ядре уходит в состояние ожидания (например, на I/O), ядро мгновенно переключается на выполнение инструкций другого потока ОС, контекст которого оно уже держит "под рукой".
Почему это важно? Простой процессора — это потерянные вычислительные ресурсы. HT/SMT позволяет заполнить простои одного потока полезной работой другого потока, значительно повышая утилизацию дорогих процессорных ядер.
Переключение потоков и его цена
Рассмотрим, как ОС управляет выполнением множества потоков на ограниченном числе ядер:
Запуск: Потоку ОС выделяется квант времени на логическом ядре. Перед началом выполнения процессор загружает контекст этого потока (регистры, указатель стека и т.д.).
Выполнение: Процессор выполняет инструкции потока.
Прерывание: По истечении кванта времени, при возникновении I/O операции или более приоритетной задачи, ОС решает прервать текущий поток.
Переключение контекста (Context Switch): Это критически важная и дорогая операция:
Шаг 1: Текущий контекст потока (все его состояние) сохраняется в память.
Шаг 2: Контекст следующего потока, выбранного для выполнения, загружается из памяти в процессор.
Возобновление: Процессор начинает выполнять инструкции нового потока с точки, сохраненной в его восстановленном контексте.
Почему переключение дорого? Операция сохранения/загрузки большого объема данных (регистры, состояние) требует тысяч тактов процессора. Частые переключения контекста могут съедать значительную часть процессорного времени, снижая общую производительность системы.
Создание потока — тоже не бесплатно.