Дата написания 27.07.2014.
Текст представляет собой реферирование информации из книги [I] CLR via C# Richter. Где со страницы 744-750 автор просто жжёт.
0. На создание процесса система тратит несколько секунд.
1. Потоки как и любое средство виртуализации, требующее ресурсов снижает производительность [I, стр. 745]
Для Windows
2. Ядро потока (therad kernel) -- регистры и информация из ядра занимает ~700 байт для x86, ~1240 байт для x64, ~2500 байт для IA64
3. Окружение потока (Thread Environment Block, TEB) -- информация потока аналогчиная 2, но в контексте userspace. Занимает одну страницу (4 КБ для x86, x64), (8 КБ для IA64)
GDI хендлы, OpenGL хендлы. Цепочка для обработки исключений. (Structured Exception Handling имеется ввиду)
4. Стек пользовательского режима (по учмолчанию 1 MB).
В Windows по умолчанию для бинарей лишь резервируется эта память в 1МБ, но реально выделяется только одной реальной странице, и одной сторожевой ~8 КБ.
http://wm-help.net/books-online/book/59464/59464-9.html#h16
Однако CLR резервирует и комитит эту память, требуя реальный 1 МБ. Сделано для надежности и более быстрого быстродействия.
5. Стек режима ядра
12 Кбайт -- x86, 24 Кбайт -- x64
6. Политика Windows -- уведомить все загруженные dll о создании нового потока и удалении потока
7. Время выданное на выполнение потока приблизительно около ~30 мс.
8. Пока работает один поток -- его код и данные находятся в кеше процессора. Однако новый поток скорее всего исполняет другой код, и данные, которых нет в кеше.
9. Потоки обеспечивают изоляцию. Проще писать и юзать потоки для создания параллельного выполнения.
А) В Microsoft Outlook -- грузятся ~250 dll. По уничтожения и созданию потока пункт 6 выше никто не отменял.
Б) Никакого выигрыша в свичиньи потоков выполняемых процессором нет. Нужно только чтобы иметь быстрореагирующую на действия пользователя ось, и надежную ось.
В) Поток не обязательно воспользуется всем кватном времени. Если поток ожидает ввода-вывода - то такт у него отнимается.
Г) НАДО ПОНИМАТЬ, ЧТО БЕЗ ПОТОКОВ ТОЖЕ НЕ ОБОЙТИСЬ!
Д) На компьютере с несколькими процессорами, может исполняться несколько потоков одновременно.
E) Если во главу ставить производительность => То кол-во потоков должно быть равно количеству ядер.
Ж) Практику с огромным количеством потоков пора заканчивать.
З) [I, Стр. 765] показывает взаимосвязь приоритета потока/процесса <=> число приоритета. Не показанные приоритеты относятся к kernel space. Диспетчиризация в винде происходит на уровне потоков. Повышенным приоритетом следует пользоваться только на очень короткий промежуток времени
И) 32-разрядное приложение при настройках по умолчанию, может обслужить только 1360 потоков. Потом просто кончится виртуальное пространство. При использовании модели асинхронной обработки кол-во клиентов одновременно подключенных с 1360 можно увеличить до 4 миллионов, до проблем возникновения с памятью. [I, стр. 823]
1. При сборке мусора все потоки приостанавливаются. Происходит просмотр стеков в поиске корней. Помечает объекты, снова просматривает стеки и возобновляет дальнейшую работу => Избавившись от потоков вы повысите быстродействие работы сборщика мусора.
2. В настоящее время CLR потоки не сильно отличаются от Windows потоков [I, стр. 757], но не исключено что в будущем они разойдутся.
3. Лучше всего использовать пулы потоков (thread pool)
4. Windows и так не является системой реального времени, и CLR с непонятным запуском GC только уводит системы от этого названия. [I, стр. 763]
5. Потоковая модель не навязывается приложениями ASP.NET, Console, XML веб-службы, НО навязывается гуёвыми программами (SilverLight, Windows Forms, WPF)
АСИНХРОННЫЕ ОПЕРАЦИИ
1. В приложениях SilverLight все операции ввода-вывода асинхронны [I, стр. 818]
2. Асинхронные операции - ключ к высокопроизводительным системам, выполняющие большое кол-ва операций благодаря небольшому кол-ву потоков
3. Поддержка Asyncronous Model Programming пронизывает модель FCL
АСИНХРОННЫЕ ОПЕРАЦИИ В ASP.NET WEB FORMS (стр. 833)
1. В .aspx файле добавить к Директиве Page "Async=True"
2. Вызывать в коде AddOnPreRenderCompleteAsync и передать имена методов Begin***, End***
3. В Begin вызввать асинхронную операция и позволить потоку пула вернутьсяв пул.
4. По завершению будет вызыван End***. Взять данные и выдать клиенту.
АСИНХРОННАЯ РЕАЛИЗАЦИЯ ВЕБ-СЛУЖБЫ
1. Превратить веб-метод Begin***, End***, пометив их [WebMethdod=true]
0. При создании процесса без указания абсолютного пути до образа, происходит поиск образа приложения, по следующим местам:
- Каталог, содержащий ЕХЕ-файл вызывающего процесса
- Текущий каталог вызывающего процесса
- Системный каталог Windows
- Основной каталог Windows
- Каталоги, перечисленные в переменной окружения PATH
1. Точка входа процесса *CRTStartup делегирует дальнейшие действия пользовательской точки входа main, WinMain.
Исходный код библиотеки crt под windows, и стартовые функции находятся в файле CRt0.c
https://docs.microsoft.com/ru-ru/cpp/c-runtime-library/crt-initialization
CRTStartup ответсвенна за конструирование глобальных и статических обхектов. Похожая ситупция наблюдается при подключении DLL, а именно если вы даже зарегистрировали точку входа DllMain, то система в первую очередь вызовет _DllMainCRTStartup.
2. После выхода из main дёргается exit(), вызывающий зарегистрированные обработчики на atexit. Вызываются деструкторы глобальных и статических объектов в C++. Вызывается ExitProcess.
3. Процесс можно завершить четырьмя способами:
- Входная функция первичного потока возвращает управление (рекомендуемый способ)
- Один из потоков процесса вызывает функцию ExitProcess (нежелательный способ)
- Поток другого процесса вызывает функцию TerminateProcess (тоже нежелательно)
- Все потоки процесса умирают по своей воле (большая редкость). Если в процессе не исполняется ни один поток, ОС немедленно завершает процесс.
Поток можно завершить четырьмя способами:
- Функция потока возвращает управление (рекомендуемый способ)
- Поток самоуничтожяется вызовом функции ExitThread (нежелательный способ)
- Лдин из потоков данного или стороннего процесса вызывает функцию TerminateThread (нежелательный способ)
- Завершается процесс, содержащий данный поток (тоже нежелательно)
4. Различные WinApi функции принимают HMODULE, или HINSTANCE. Обе они хранят базовым адрес загруженного модуля.
5. Переменные окружения
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\Environment - список переменных окружения системы.
HKEY_CURRENT_USER\Environment - список переменных окружения пользователя, в настоящее время зарегистрированного в системе.
6. Про старт потока
Вызов CreateThread заставляет систему создать объект ядра "поток". При этом счетчику числа его пользователей присваивается начальное значение, равное 2.
Объект ядра "поток" уничтожается только после того, как прекращается выполнение потока и закрывается описатель, возвращенный функцией CreateThread.
Также инициализируются другие свойства этого объекта - счетчик числа простоев (suspension count) получает значение 1, а код завершения — значение STILL_ACTIVE (0x103) И, наконец, объект переводится в состояние "занято".
Создав объект ядра "поток", система выделяет стеку потока память из адресного пространства процесса и записывает в его самую верхнюю часть два значения.
Стеки потоков всегда строятся от старших адресов памяти к младшим.
Первое из них является значением параметра pvParam, переданного Вами функции CreateThread, а второе — это содержимое параметра pfnStartAddr, который Вы тоже передаете в CreateThread.
У каждого потока собственный набор регистров процессора, называемый контекстом потока. Указатель команд (IP) и указатель стека (SP) — два самых важных регистра в контексте потока.
Когда система инициализирует объект ядра "поток", указателю стека в структуре CONTEXT присваивается тот адрес, по которому в стек потока было записано значение pfnStartAddr, а указателю команд — адрес недокументированной (и неэкспортируемой) функции BaseThreadStart. Эта функция содержится в модуле Kernel32.dll, где, кстати, реализована и функция CreateTbread.
Вот главное, что делает BaseThreadStart:
VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam)
{
__try
{
ExitThread((pfnStartAddr)(pvParam));
}
_except(UnhandledExceptionFilter(GetExceptionInformation()))
{
ExitProcess(GetExceptionCode());
}
}
После инициализации потока система проверяет, был ли передан функции CreateThread флаг CREATE_SUSPENDED Если нет, система обнуляет его счетчик числа простоев, и потоку может быть выделено процессорное время. Далее система загружает в регистры процессора значения, сохраненные в контексте потока С этого момента поток может выполнять код и манипулировать данными в адресном пространстве своего процесса.
Поскольку указатель команд нового потока установлен на BaseThreadStart, именно с этой функции и начнется выполнение потока. Глядя на ее прототип, можно подумать, будто BaseThreadStart передаются два параметра, а значит, она вызывается из какой-то другой функции, но это не так. Новый поток просто начинает с нее свою работу. BaseThreadStart получает доступ к двум параметрам, которые появляются у нее потому, что операционная система записала соответствующие значения в стек потока, а через него параметры как раз и передаются функциям.
Правда, на некоторых аппаратных платформах параметры передаются не через стек, а с использованием определенных регистров процессора.
На таких аппаратных платформах система — прежде чем разрешить потоку выполнение функции BaseThreadStart — инициализирует нужные регистры процессора.
Когда новый поток выполняет BaseThreadStart, происходит следующее:
1. Ваша функция потока включается во фрейм структурной обработки исключений (SEH-фрейм), благодаря чему любое исключение, если оно происходит в момент выполнения вашего потока, получает хоть какую-то обработку, предлагаемую системой по умолчанию.
2. Система обращается к Вашей функции потока, передавая ей параметр pvParam, который Вы ранее передали функции CreateThread.
3. Когда Ваша функция потока возвращает управление, BaseThreadStart вызывает ExitThread, передавая ей значение, возвращенное Вашей функцией.
4. Счетчик числа пользователей объекта ядра "поток" уменьшается на 1, и выполнение потока прекращается.
5. Если Ваш поток вызывает необрабатываемое им исключение, его обрабатывает SEH-фрейм, построенный функцией BaseThreadStart .Обычно в результате этого появляется окно с каким-нибудь сообщением, и, когда пользователь закрывает его, BaseThreadStart вызывает ExitProcess и завершает весь процесс, а не только тот поток, в котором произошло исключение.
6. Обратите внимание, что из BaseThreadStart поток вызывает либо ExitThread, либо ExitProcess А это означает, что поток никогда не выходит из данной функции; он всегда уничтожается внутри нее. Вот почему BaseThreadStart нет возвращаемого значения — она просто ничего не возвращает.
7. Кстати, именно благодаря BaseThreadStart Ваша функция потока получает возможность вернуть управление по окончании своей работы. Сама BaseThreadStart не возвращает управление. Иначе возникло бы нарушение доступа, так как в стеке потока нет ее адреса возврата.
При инициализации первичного потока его указатель команд устанавливается на другую недокументированную функцию — BaseProcessStart Она почти идентична BaseThreadStart и выглядит примерно так:
VOID BaseProcessStart(PPROCESS_START_BOUTINE pfnStartAddr)
{
__try
{
ExitThread((pfnStartAdd r)());
}
_except(UnhandledFxceptionFilter(GetExceptionInformation()))
{
ExitProcess(GettxceptionCode());
}
}
Единственное различие между этими функциями в отсутствии ссылки на параметр pvParam. Функция BaseProcessStartЖ
- обращается к стартовому коду библиотеки С/С++, который выполняет необходимую инициализацию
- а затем вызывает вашу входную функцию main, wmain, WinMain или wWinMain.
- Когда входная функция возвращает управление, стартовый код библиотеки С/С++ вызываст ExitProcess.
- Поэтому первичный поток приложения, написанного на С/С++, никогда не возвращается в BaseProcessStart.9