- •Сборки (assembly) в среде .Net. Проблема версионности сборок и ее решение.
- •Номер версии в .Net
- •Сведения о версии
- •Номер версии сборки
- •Информационная версия сборки
- •Общая система типов данных в среде .Net. Размерные и ссылочные типы данных. Типы, переменные и значения
- •Пользовательские типы
- •Система общих типов cts
- •Ссылочные типы
- •Типы литеральных значений
- •Неявные типы, анонимные типы и типы, допускающие значение null
- •Упаковка и распаковка размерных типов данных в среде .Net.
- •Производительность
- •Упаковка–преобразование
- •Распаковка-преобразование
- •Ссылочные типы данных. Объектная модель в среде .Net и языке c#.
- •Модели ручной и автоматической утилизации динамической памяти, их сравнительная характеристика. Модель с ручным освобождением памяти
- •Модель с автоматической «сборкой мусора»
- •Модель автоматической утилизации динамической памяти, основанная на сборке мусора. Проблема недетерминизма.
- •Модель автоматической утилизации динамической памяти, основанная на аппаратной поддержке (тегированной памяти).
- •Сборка мусора в среде .Net. Построение графа достижимых объектов.
- •Сборка мусора в среде .Net. Механизм поколений объектов.
- •Модель детерминированного освобождения ресурсов в среде .Net. Интерфейс iDisposable и его совместное использование с завершителем (методом Finalize).
- •«Мягкие ссылки» и кэширование данных в среде .Net.
- •Краткие и длинные слабые ссылки
- •Краткая ссылка
- •Длинная ссылка
- •Правила использования слабых ссылок
- •Динамические массивы в среде .Net и языке c#.
- •Приведение типов в массивах
- •Все массивы неявно реализуют /Enumerable, /Collection и iList
- •Передача и возврат массивов
- •Создание массивов с ненулевой нижней границей
- •Производительность доступа к массиву
- •Небезопасный доступ к массивам и массивы фиксированного размера
- •Делегаты в среде .Net и механизм их работы. Знакомство с делегатами
- •Использование делегатов для обратного вызова статических методов
- •Использование делегатов для обратного вызова экземплярных методов
- •Правда о делегатах
- •Использование делегатов для обратного вызова множественных методов (цепочки делегатов)
- •Поддержка цепочек делегатов в с#
- •Расширенное управление цепочкой делегатов
- •Упрощение синтаксиса работы с делегатами в с#
- •Упрощенный синтаксис № 1: не нужно создавать объект-делегат
- •Упрощенный синтаксис № 2: не нужно определять метод обратного вызова
- •Упрощенный синтаксис № 3: не нужно определять параметры метода обратного вызова
- •Упрощенный синтаксис № 4: не нужно вручную создавать обертку локальных переменных класса для передачи их в метод обратного вызова
- •Делегаты и отражение
- •События в среде .Net; реализация событий посредством делегатов. События
- •Этап 1: определение типа, который будет хранить всю дополнительную информацию, передаваемую получателям уведомления о событии
- •Этап 2: определение члена-события
- •Этап 3: определение метода, ответственного за уведомление зарегистрированных объектов о событии
- •Этап 4: определение метода, транслирующего входную информацию в желаемое событие
- •Как реализуются события
- •Создание типа, отслеживающего событие
- •События и безопасность потоков
- •Явное управление регистрацией событий
- •Конструирование типа с множеством событий
- •Исключительные ситуации и реакция на них в среде .Net. Достоинства
- •Механика обработки исключений
- •Блок try
- •Блок catch
- •Блок finally
- •Генерация исключений
- •Определение собственных классов исключений
- •Исключения в платформе .Net Framework
- •Исключения и традиционные методы обработки ошибок
- •Управление исключениями средой выполнения
- •Фильтрация исключений среды выполнения
- •21 Средства многопоточного программирования в среде .Net. Автономные потоки. Пул потоков.
- •Создание и использование потоков
- •Запуск и остановка потоков
- •Методы управления потоками
- •Безопасные точки
- •Свойства потока
- •Потоки Windows в clr
- •К вопросу об эффективном использовании потоков
- •Пул потоков в clr
- •Ограничение числа потоков в пуле
- •22. Асинхронные операции в среде .Net. Асинхронный вызов делегатов.
- •23. Синхронизация программных потоков в среде .Net. Блокировки.
- •Двойная блокировка
- •Класс ReaderWriterLock
- •Использование объектов ядра Windows в управляемом коде
- •Вызов метода при освобождении одного объекта ядра
- •24. Синхронизация программных потоков в среде .Net. Атомарные (Interlocked-операции). Семейство lnterlocked-методов
- •25. Прерывание программных потоков в среде .Net. Особенности исключительной ситуации класса ThreadAbortException.
- •26. Мониторы в среде .Net. Ожидание выполнения условий с помощью методов Wait и Pulse. Класс Monitor и блоки синхронизации
- •«Отличная» идея
- •Реализация «отличной» идеи
- •Использование класса Monitor для управления блоком синхронизации
- •Способ синхронизации, предлагаемый Microsoft
- •Упрощение кода c# при помощи оператора lock
- •Способ синхронизации статических членов, предлагаемый Microsoft
- •Почему же «отличная» идея оказалась такой неудачной
- •Целостность памяти, временный доступ к памяти и volatile-поля
- •Временная запись и чтение
- •Поддержка volatile-полей в с#
- •27. Асинхронный вызов делегатов.
- •Общие типы (Generics)
- •Инфраструктура обобщений
- •Открытые и закрытые типы
- •Обобщенные типы и наследование
- •Проблемы с идентификацией и тождеством обобщенных типов
- •«Распухание» кода
- •Обобщенные интерфейсы
- •Обобщенные делегаты
- •Обобщенные методы
- •Логический вывод обобщенных методов и типов
- •Обобщения и другие члены
- •Верификация и ограничения
- •Основные ограничения
- •Дополнительные ограничения
- •Ограничения конструктора
- •Другие вопросы верификации
- •Приведение переменной обобщенного типа
- •Присвоение переменной обобщенного типа значения по умолчанию
- •Сравнение переменной обобщенного типа с null
- •Сравнение двух переменных обобщенного типа
- •Использование переменных обобщенного типа в качестве операндов
- •Преимущества использования общих типов
- •29. Итераторы в среде .Net. Создание и использование итераторов.
- •Общие сведения о итераторах
Пул потоков в clr
Как отмечалось выше, создание и уничтожение потока — довольно дорогая операция в плане временных затрат. Кроме того, при наличии множества потоков впустую расходуется память и ухудшается производительность из-за того, что ОС должна планировать потоки и выполнять переключения контекста между потоками, готовыми к выполнению. К счастью, в CLR есть код для управления собственным пулом потоков. Пул потоков можно рассматривать как набор потоков, доступных для использования приложением. У каждого процесса есть один пул потоков, который используется всеми доменами AppDomain этого процесса.
При инициализации CLR пул потоков пуст. Сам пул потоков обслуживает очередь запросов на обработку. Когда приложению нужно выполнить асинхронную операцию, вызывается метод, размещающий соответствующий запрос в очередь пула потоков. Код пула потоков извлекает записи из очереди и передает их потоку из пула. Если пул пуст, создается новый поток. Создание потока снижает производительность. Но по завершении своей задачи поток из пула не уничтожается, а возвращается в пул и ожидает следующего запроса. Поскольку поток не уничтожается, производительность от этого не страдает.
Когда приложение отправляет много запросов пула потоков, пул пытается обслужить все запросы с помощью одного потока. Но, если приложение создает очередь запросов, а поток из пула не успевает их обработать, создаются дополнительные потоки. В конце концов, все запросы приложения будут обрабатываться небольшим числом потоков, и пулу не придется создавать множество потоков.
Если же приложение перестанет создавать запросы пула потоков, в пуле может оказаться много незанятых потоков, впустую занимающих ресурсы памяти. Поэтому после примерно 2 минут бездействия поток пробуждается и самоуничтожается, чтобы освободить ресурсы. Хотя самоуничтожение потока ухудшает производительность, это не столь важно, поскольку этот поток не был занят и приложение в данный момент не выполняет много работы.
Пул потоков позволяет найти золотую середину между двумя крайностями: малое число потоков экономит ресурсы, а большое — позволяет воспользоваться преимуществами многопроцессорных систем, многоядерных процессоров и технологии Hyperthreading. Пул потоков действует по эвристическому алгоритму, приспосабливаясь к текущей ситуации. Если приложение должно выполнить множество задач и есть доступные процессоры, пул создает больше потоков. При уменьшении загруженности приложения потоки из пула самоуничтожаются.
В пуле различают два типа потоков: рабочие потоки и потоки ввода-вывода. Первые используются, когда приложение запрашивает пул выполнить асинхронную вычислительную операцию (которая может включать инициацию операции ввода-вывода). А потоки ввода-вывода служат для уведомления кода о завершении асинхронной операции ввода-вывода. Это значит, что для выполнения запросов, например обращения к файлу, сетевому серверу или базе данных, используется модель АРМ (Asynchronous Programming Model).
Ограничение числа потоков в пуле
CLR позволяет разработчикам задавать максимальное число рабочих потоков и потоков ввода-вывода в пуле, а CLR гарантирует, что число созданных потоков никогда не превысит заданное.
Верхний предел числа потоков в пуле задавать не следует — это может привести к нехватке процессорного времени и даже к взаимной блокировке потоков. Только представьте: 1000 рабочих элементов в очереди блокируются одним событием, которое освобождает 1001-й элемент. Если максимальное число потоков в пуле — 1000, 1001-й рабочий элемент не выполнится, и все 1000 потоков окажутся заблокированными навсегда. Сама идея ограничения числа потоков, реализованная в CLR, неудачна, потому что из-за нее программисты впустую тратят время. В предыдущих версиях CLR максимальное число рабочих потоков и потоков ввода-вывода было ограничено небольшим значением по умолчанию, что иногда приводило к взаимной блокировке и недостатку процессорных ресурсов. К счастью, команда разработчиков CLR учла этот момент и в CLR версии 2.0 увеличила максимальное число рабочих потоков по умолчанию до 25 на каждый процессор компьютера, а число потоков ввода-вывода — до 1000. Последнее, в сущности, не накладывает никакого ограничения. Полностью убирать эти ограничения нельзя, потому что в таком случае нарушится работа приложений, созданных для предыдущих версий CLR. Кстати, в API Win32 нет средств ограничения числа потоков в пуле — по указанным выше причинам.
В классе System.Threading.ThreadPool есть несколько статических методов, которые служат для изменения числа потоков в пуле. Вот прототипы этих методов:
void GetMaxThreads(out Int32 workerThreads, out Int32 completionPortThreads);
Boolean SetMaxThreads(Int32 workerThreads, Int32 completionPortThreads);
void GetMinThreads(out Int32 workerThreads, out Int32 completionPortThreads);
Boolean SetMinThreads(Int32 workerThreads, Int32 completionPortThreads);
void GetAvailableThreads(out Int32 workerThreads, out Int32 completionPortThreads);
Метод GetMaxTbreads позволяет узнать максимальное число потоков в пуле. Для изменения этого значения служит метод SetMaxThreads. Не рекомендую вызывать эти методы. Манипуляции с максимальным числом потоков в пуле обычно ухудшают, а не улучшают производительность приложения. Если же приложению нужно больше 25 потоков на процессор, значит, в его архитектуре и способе использования потоков есть серьезные изъяны.
Вместе с тем, в пул потоков CLR заложен механизм предотвращения слишком частого создания потоков — разрешается создавать не более одного потока в 500 мс. Некоторых разработчиков это не устраивает, потому что задания из очереди обрабатываются медленнее. В этом случае в приложение можно добавить вызов метода SetMinThreads и передать в него минимальное допустимое число потоков в пуле. Пул быстро создаст указанное число потоков, а если при появлении в очереди новых заданий все потоки будут заняты, он продолжит создавать потоки со скоростью не более одного потока в 500 мс. По умолчанию минимальное число рабочих потоков и потоков ввода-вывода равно 2. Я узнал это, вызвав метод GetMinThreads.
Метод GetAvailableThreads позволяет узнать число дополнительных потоков, которые разрешено добавить в пул. Он возвращает максимальное число потоков за вычетом текущего числа потоков в пуле. Значение, возвращаемое этим методом, действительно лишь на момент вызова метода — к тому моменту, когда метод вернет управление приложению, в пул могут быть добавлены новые потоки или уничтожены имеющиеся. Этот метод вызывается в приложении лишь для диагностики, мониторинга или настройки производительности.