Работа операционной системы Windows основана на работе процессов. В этой статье разберём что такое Windows процессы, их свойства, состояния и другое.
Процессы
Процесс стоит воспринимать как контейнер с набором ресурсов для выполнения программы. То есть запускаем мы программу, для неё выделяется часть ресурсов компьютера и эта программа работает с этими ресурсами.
Процессы нужны операционной системе для многозадачности, так как программы работают в своих процессах и не мешают друг другу, при этом по очереди обрабатываются процессором.
Windows процессы состоят из следующего:
- Закрытое виртуальное адресное пространство, то есть выделенная для процесса часть оперативной памяти, которая называется виртуальной.
- Исполняемая программа выполняя свой код, помещает его в виртуальную память.
- Список открытых дескрипторов. Процесс может открывать или создавать объекты, например файлы или другие процессы. Эти объекты нумеруются, и их номера называют дескрипторами. Ссылаться на объект по дескриптору быстрее, чем по имени.
- Контекст безопасности. Сюда входит пользователь процесса, группа, привилегии, сеанс и другое.
- Идентификатор процесса, то есть его уникальный номер.
- Программный поток (как минимум один или несколько). Чтобы процесс хоть что-то делал, в нем должен существовать программный поток. Если потока нет, значит что-то пошло не так, возможно процесс не смог корректно завершиться, или стартовать.
У процессов есть еще очень много свойств которые вы можете посмотреть в «Диспетчере задач» или «Process Explorer«.
Процесс может быть в различных состояниях:
- Выполняется — обычно все фоновые процессы будут в этом состоянии, а если процесс с окошком, то значит что приложение готово принимать данные от пользователя.
- Приостановлен — означает что все потоки процесса находятся в приостановленном состоянии. Приложения Windows Apps переходят в это состояние при сворачивании окна для экономии ресурсов.
- Не отвечает — означает что программный поток не проверял свою очередь сообщений более 5 секунд. Поток может быть занят работой и интенсивно загружать процессор, или может ожидать операции ввода/вывода. При этом окно приложения зависает.
В Windows существуют процессы трёх типов:
- Приложения. Процессы запущенных приложений. У таких приложений есть окно на рабочем столе, которое вы можете свернуть, развернуть или закрыть.
- Фоновые процессы. Такие процессы работают в фоне и не имеют окна. Некоторые процессы приложений становятся фоновыми, когда вы сворачиваете их в трей.
- Процессы Windows. Процессы самой операционной системы, например «Диспетчер печати» или «Проводник».
Дерево процессов
В Windows процессы знают только своих родителей, а более древних предков не знают.
Например у нас есть такое дерево процессов:
Процесс_1 |- Процесс_2 |- Процесс_3
Если мы завершим дерево процессов «Процесс_1«, то завершатся все процессы. Потому что «Процесс_1» знает про «Процесс_2«, а «Процесс_2» знает про «Процесс_3«.
Если мы вначале завершим «Процесс_2«, а затем завершаем дерево процессов «Процесс_1«, то завершится только «Процесс_1«, так как между «Процесс_1» и «Процесс_3» не останется связи.
Например, запустите командную строку и выполните команду title parrent чтобы изменить заголовок окна и start cmd чтобы запустить второе окно командной строки:
>title parrent >start cmd
Измените заголовок второго окна на child и из него запустите программу paint:
>title child >mspaint
В окне командной строке child введите команду exit, окно закроется а paint продолжит работать:
>exit
После этого на рабочем столе останутся два приложения, командная строка parrent и paint. При этом parrent будет являться как бы дедом для paint.
Запустите «Диспетчер задач», на вкладке «Процессы» найдите процесс «Обработчик команд Windows», разверните список и найдите «parrent«. Затем нажмите на нём правой копкой мыши и выберите «Подробно»:
Вы переключитесь на вкладку «Подробно» с выделенным процессом «cmd.exe«. Нажмите правой кнопкой по этому процессу и выберите «Завершить дерево процессов»:
Окно командной строки Parrent завершится а Paint останется работать. Так мы убедились что связи между первым процессом и его внуком нет, если у внука нет непосредственного родителя.
Потоки
На центральном процессоре обрабатываются не сами процессы, а программные потоки. Каждый поток, это код загруженный программой. Программа может работать в одном потоке или создавать несколько. Если программа работает в несколько потоков, то она может выполняться на разных ядрах процессора. Посмотреть на потоки можно с помощью программы Process Explorer.
Поток содержит:
- два стека: для режима ядра и для пользовательского режима;
- локальную памятью потока (TLS, Thread-Local Storage);
- уникальный идентификатор потока (TID, Thread ID).
Приложение может создать дополнительный поток, например, когда у приложения есть графический интерфейс, который работает в одном потоке и ожидает от пользователя ввода каких-то данных, а второй поток в это время занимается обработкой других данных.
Изучение активности потока важно, если вам нужно разобраться, почему тот или иной процесс перестал реагировать, а в процессе выполняется большое число потоков. Потоков может быть много в следующих процессах:
- svchost.exe — главный процесс для служб Windows.
- dllhost.exe — отвечает за обработку приложений, использующих динамически подключаемые библиотеки. Также отвечает за COM и .NET. И ещё управляет процессами IIS.
- lsass.exe — отвечает за авторизацию локальных пользователей, попросту говоря без него вход в систему для локальных пользователей будет невозможен.
Волокна и планирование пользовательского режима
Потоки выполняются на центральном процессоре, а за их переключение отвечает планировщик ядра. В связи с тем что такое переключение это затратная операция. В Windows придумали два механизма для сокращения таких затрат: волокна (fibers) и планирование пользовательского режима (UMS, User Mode Scheduling).
Во-первых, поток с помощью специальной функции может превратится в волокно, затем это волокно может породить другие волокна, таким образом образуется группа волокон. Волокна не видимы для ядра и не обращаются к планировщику. Вместо этого они сами договариваются в какой последовательности они будут обращаться к процессору. Но волокна плохо реализованы в Windows, большинство библиотек ничего не знает о существовании волокон. Поэтому волокна могут обрабатываться как потоки и начнутся различные сбои в программе если она использует такие библиотеки.
Потоки UMS (User Mode Scheduling), доступные только в 64-разрядных версиях Windows, предоставляют все основные преимущества волокон при минимуме их недостатков. Потоки UMS обладают собственным состоянием ядра, поэтому они «видимы» для ядра, что позволяет нескольким потокам UMS совместно использовать процессор и конкурировать за него. Работает это следующим образом:
- Когда двум и более потокам UMS требуется выполнить работу в пользовательском режиме, они сами могут периодически уступать управление другому потоку в пользовательском режиме, не обращаясь к планировщику. Ядро при этом думает что продолжает работать один поток.
- Когда потоку UMS все таки нужно обратиться к ядру, он переключается на специально выделенный поток режима ядра.
Задания
Задания Windows (Job) позволяют объединить несколько процессов в одну группу. Затем можно этой группой управлять:
- устанавливать лимиты (на память или процессорное время) для группы процессов входящих в задание;
- останавливать, приостанавливать, запускать такую группу процессов.
Посмотреть на задания можно с помощью Process Explorer.
Диспетчер задач
Чаще всего для получения информации о процессе мы используем «Диспетчер задач». Запустить его можно разными способами:
- комбинацией клавиш Ctrl+Shift+Esc;
- щелчком правой кнопкой мыши на панели задач и выборе «Диспетчер задач»;
- нажатием клавиш Ctrl+Alt+Del и выборе «Диспетчер задач»;
- запуском исполняемого файла C:\Windows\system32\Taskmgr.exe.
При первом запуске диспетчера задач он запускается в кратком режиме, при этом видны только процессы имеющие видимое окно. При нажатие на кнопку «Подробнее» откроется полный режим:
В полном режиме на вкладке «Процессы» виден список процессов и информация по ним. Чтобы получить больше информации можно нажать правой кнопкой мышки на заголовке и добавить столбцы:
Чтобы получить еще больше информации можно нажать правой кнопкой мышки на процессе и выбрать «Подробно». При этом вы переключитесь на вкладку «Подробности» и этот процесс выделится.
На вкладке «Подробности» можно получить ещё больше информации о процессе. А также здесь также можно добавить колонки с дополнительной информацией, для этого нужно щелкнуть правой кнопкой мыши по заголовку и нажать «Выбрать столбцы»:
Process Explorer
Установка и подготовка к работе
Более подробную информацию о процессах и потоках можно получить с помощью программы Process Explorer из пакета Sysinternals. Его нужно скачать и запустить.
Некоторые возможности Process Explorer:
- информация по правам процесса: кто владелец процесса, у кого есть доступ к нему;
- выделение разными цветами процессов и потоков, для удобного восприятия информации:
- процессы служб — розовый;
- ваши собственные процессы — синий;
- новые процессы — зелёный;
- завершенные процессы — красный;
- список файлов открытых процессом;
- возможность приостановки процесса или потока;
- возможность уничтожения отдельных потоков;
- поиск процессов создающих наибольшую нагрузку на процессор;
- отображение списка процессов в виде дерева, а также алфавитная сортировка и сортировка в обратном порядке;
- возможность посмотреть:
- число дескрипторов у процесса;
- активность потоков в процессе;
- подробную информация о распределении памяти.
Запустите Process Explorer:
Далее нужно настроить сервер символических имен. Если это не сделать, при двойном щелчке на процессе, на вкладке Threads (потоки) вы получите сообщение о том, что символические имена не настроены:
Для начала скачиваем установщик «Пакет SDK для Windows 10».
Устанавливать все не нужно, достаточно при установки выбрать «Debugging Tools for Windows«:
Для настройки символических имен перейдите в меню Options / Configure / Symbols. Введите путь к библиотеке Dbghelp.dll, которая находится внутри установленного «Пакета SDK для Windows 10» по умолчанию:
- C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Dbghelp.dll.
И путь к серверу символической информации:
- srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
При этом:
- C:\Symbols — путь к кеширующей локальной папке;
- http://msdl.microsoft.com/download/symbols — сервер microsoft.
Некоторые основные настройки Process Explorer:
- Смена цветового выделения — Options / Configure Colors.
- Выбор колонок с информацией о процессах — View / Select Columns.
- Сортировка процессов — нужно щелкнуть на заголовке столбца Process, при первом щелчке сортировка будет в алфавитном порядке, при втором в обратном порядке, при третьем вернется в вид дерева.
- Просмотр только своих процессов — View / снять галочку Show Processes from All Users.
- Настройка времени выделения только что запущенных процессов и завершённых — Options / Difference Highlight Duration / введите количество секунд.
- Чтобы исследователь процесс подробнее можно дважды щелкнуть на нем и посмотреть информацию на различных вкладках.
- Открыть нижнюю панель для просмотра открытых дескрипторов или библиотек — Vies / Show Lower Panel.
Потоки в Process Explorer
Потоки отдельного процесса можно увидеть в программе Process Explorer. Для этого нужно дважды кликнуть по процессу и в открывшемся окне перейти на вкладку «Threads»:
В колонках видна информация по каждому потоку:
- TID — идентификатор потока.
- CPU — загрузка процессора.
- Cycles Delta — общее количество циклов процессора, которое этот процесс использовал с момента последнего обновления работы Process Explorer. Скорость обновления программы можно настроить, указав например 5 минут.
- Suspend Count — количество приостановок потока.
- Service — название службы.
- Start Address — начальный адрес процедуры, который начинает выполнение нового потока. Выводится в формате:«модуль!функция».
При выделении потока, снизу показана следующую информация:
- Идентификатор потока.
- Время начала работы потока.
- Состояние потока.
- Время выполнения в режиме ядра и в пользовательском режиме.
- Счетчик переключения контекста для центрального процессора.
- Количество циклов процессора.
- Базовый приоритет.
- Динамический приоритет (текущий).
- Приоритет ввода / вывода.
- Приоритет памяти.
- Идеальный процессор (предпочтительный процессор).
Есть также кнопки:
- Stack — посмотреть стек процесса;
- Module — посмотреть свойства запущенного исполняемого файла;
- Permission — посмотреть права на поток;
- Kill — завершить поток;
- Suspend — приостановить поток.
Задания в Process Explorer
Process Explorer может выделить процессы, управляемые заданиями. Чтобы включить такое выделение откройте меню «Options» и выберите команду «Configure Colors», далее поставьте галочку «Jobs»:
Более того, страницы свойств таких процессов содержат дополнительную вкладку Job с информацией о самом объекте задания. Например приложение Skype работает со своими процессами как за заданием:
Запустите командную строку и введите команду:
>runas /user:<домен>\<пользователь> cmd
Таким образом вы запустите еще одну командную строку от имени этого пользователя. Служба Windows, которая выполняет команды runas, создает безымянное задание, чтобы во время выхода из системы завершить процессы из задания.
В новой командной строке запустите блокнот:
>notepad.exe
Далее запускаем Process Explorer и находим такое дерево процессов:
Как видим, процесс cmd и notepad это процессы связанные с каким-то заданием. Если дважды кликнуть по любому из этих процессов и перейти на вкладку Job, то мы увидим следующее:
Тут видно что эти два процесса работают в рамках одного задания.
Вернуться к оглавлению
Сводка
Имя статьи
Процессы Windows
Описание
Работа операционной системы Windows основана на работе процессов. В этой статье разберём что такое Windows процессы, их свойства, состояния и другое
Ниже представлена не простая расшифровка доклада с семинара CLRium, а переработанная версия для книги .NET Platform Architecture. Той её части, что относится к потокам.
Потоки и планирование потоков
Что такое поток? Давайте дадим краткое определение. По своей сути поток это:
- Средство параллельного относительно других потоков исполнения кода;
- Имеющего общий доступ ко всем ресурсам процесса.
Очень часто часто слышишь такое мнение, что потоки в .NET — они какие-то абсолютно свои. И наши .NET потоки являются чем-то более облегчённым чем есть в Windows. Но на самом деле потоки в .NET являются самыми обычными потоками Windows (хоть Windows thread id и скрыто так, что сложно достать). И если Вас удивляет, почему я буду рассказывать не-.NET вещи в хабе .NET, скажу вам так: если нет понимания этого уровня, можно забыть о хорошем понимании того, как и почему именно так работает код. Почему мы должны ставить volatile, использовать Interlocked и SpinWait. Дальше обычного lock
дело не уйдёт. И очень даже зря.
Давайте посмотрим из чего они состоят и как они рождаются. По сути поток — это средство эмуляции параллельного исполнения относительно других потоков. Почему эмуляция? Потому, что поток как бы странно и смело это ни звучало — это чисто программная вещь, которая идёт из операционной системы. А операционная система создаёт этот слой эмуляции для нас. Процессор при этом о потоках ничего не знает вообще.
Задача процессора — просто исполнять код. Поэтому с точки зрения процессора есть только один поток: последовательное исполнение команд. А задача операционной системы каким-либо образом менять поток т.о. чтобы эмулировать несколько потоков.
Поток в физическом понимании
«Но как же так?», — скажите вы, — «во многих магазинах и на различных сайтах я вижу запись «Intel Xeon 8 ядер 16 потоков». Говоря по-правде это — либо скудность в терминологии либо — чисто маркетинговый ход. На самом деле внутри одного большого процессора есть в данном случае 8 ядер и каждое ядро состоит из двух логических процессоров. Такое доступно при наличии в процессоре технологии Hyper-Threading, когда каждое ядро эмулирует поведение двух процессоров (но не потоков). Делается это для повышения производительности, да. Но по большому счёту если нет понимания, на каких потоках идут расчёты, можно получить очень не приятный сценарий, когда код выполняется со скоростью, ниже чем если бы расчёты шли на одном ядре. Именно поэтому раздача ядер идёт +=2 в случае Hyper-Threading. Т.е. пропуская парные ядра.
Технология эта — достаточно спорная: если вы работаете на двух таких псевдо-ядрах (логических процессорах, которые эмулируются технологией Hyper-Threading), которые при этом находятся на одном физическом ядре и работают с одной и той-же памятью, то вы будете постоянно попадать в ситуацию, когда второй логический процессор так же пытается обратиться к данной памяти, создавая блокировку либо попадая в блокировку, т.к. поток, находящийся на первом ядре работает с той же памятью.
Возникает блокировка совместного доступа: хоть и идёт эмуляция двух ядер, на самом-то деле оно одно. Поэтому в наихудшем сценарии эти потоки исполняются по очереди, а не параллельно.
Так если процессор ничего не знает о потоках, как же достигается параллельное исполнение потоков на каждом из его ядер? Как было сказано, поток — средство операционной системы выполнять на одном процессоре несколько задач одновременно. Достигается параллелизм очень быстрым переключением между потоками в течение очень короткого промежутка времени. Последовательно запуская на выполнение код каждого из потоков и делая это достаточно часто, операционная система достигает цели: делает их исполнение псевдопараллельным, но параллельным с точки зрения восприятия человека. Второе обоснование существования потоков — это утверждение, что программа не так часто срывается в математические расчёты. Чаще всего она взаимодействует с окружающим её миром: различным оборудованием. Это и работа с жёстким диском и вывод на экран и работа с клавиатурой и мышью. Поэтому чтобы процессор не простаивал, пока оборудование сделает то, чего хочет от него программа, поток можно на это время установить в состояние блокировки: ожидания сигнала от операционной системы, что оборудование сделало то, что от него просили. Простейший пример этого — вызов метода Console.ReadKey()
.
Если заглянуть в диспетчер задач Windows 10, то можно заметить, что в данный момент в вашей системе существует около 1,5 тысячи потоков. И если учесть, что квант на десктопе равен 20 мс, а ядер, например, 4, то можно сделать вывод, что каждый поток получает 20 мс работы 1 раз в 7,5 сек… Ну конечно же, нет. Просто почти все потоки чего-то ждут. То ввода пользователя, то изменения ключей реестра… В операционной системе существует очень много причин, чтобы что-либо ждать.
Так что пока одни потоки в блокировке, другие — что-то делают.
Создание потоков
Простейшая функция создания потоков в пользовательском режиме операционной системы — CreateThread
. Эта функция создаёт поток в текущем процессе. Вариантов параметризации CreateThread
очень много и когда мы вызываем new Thread()
, то из нашего .NET кода вызывается данная функция операционной системы.
В эту функцию передаются следующие атрибуты:
1) Необязательная структура с атрибутами безопасности:
- Дескриптор безопасности (SECURITY_ATTRIBUTES) + признак наследуемости дескриптора.
В .NET его нет, но можно создать поток через вызов функции операционной системы;
2) Необязательный размер стека:
- Начальный размер стека, в байтах (система округляет это значение до размера страницы памяти)
Т.к. за нас размер стека передаёт .NET, нам это делать не нужно. Это необходимо для вызовов методов и поддержки памяти.
3) Указатель на функцию — точка входа нового потоками
4) Необязательный аргумент для передачи данных функции потока.
Из того, что мы не имеем в .NET явно — это структура безопасности с атрибутами безопасности и размер стэка. Размер стэка нас мало интересует, но атрибуты безопасности нас могут заинтересовать, т.к. сталкиваемся мы с ними впервые. Сейчас мы рассмотривать их не будем. Скажу только, что они влияют на возможность изменения информации о потоке средствами операционной системы.
Если мы создаём любым способом: из .NET или же вручную, средствами ОС, мы как итог имеем и ManageThreadId и экземпляр класса Thread.
Также у этой функции есть необязательный флаг: CREATE_SUSPENDED
— поток после создания не стартует. Для .NET это поведение по умолчанию.
Помимо всего прочего существует дополнительный метод CreateRemoteThread
, который создаёт поток в чужом процессе. Он часто используется для мониторинга состояния чужого процесса (например программа Snoop). Этот метод создаёт в другом процессе поток и там наш поток начинает исполнение. Приложения .NET так же могут заливать свои потоки в чужие процессы, однако тут могут возникнуть проблемы. Первая и самая главная — это отсутствие в целевом потоке .NET runtime. Это значит, что ни одного метод фреймворка там не будет: только WinAPI и то, что вы написали сами. Однако, если там .NET есть, то возникает вторая проблема (которой не было раньше). Это — версия runtime. Необходимо: понять, что там запущено (для этого необходимо импортировать не-.NET методы runtime, которые написаны на C/C++ и разобраться, с чем мы имеем дело). На основании полученной информации подгрузить необходимые версии наших .NET библиотек и каким-то образом передать им управление.
Я бы рекомендовал вам поиграться с задачкой такого рода: вжиться в код любого .NET процесса и вывести куда-либо сообщение об удаче внедрения (например, в файл лога)
Планирование потоков
Для того чтобы понимать, в каком порядке исполнять код различных потоков, необходима организация планирования тих потоков. Ведь система может иметь как одно ядро, так и несколько. Как иметь эмуляцию двух ядер на одном так и не иметь такой эмуляции. На каждом из ядер: железных или же эмулированных необходимо исполнять как один поток, так и несколько. В конце концов система может работать в режиме виртуализации: в облаке, в виртуальной машине, песочнице в рамках другой операционной системы. Поэтому мы в обязательном порядке рассмотрим планирование потоков Windows. Это — настолько важная часть материала по многопоточке, что без его понимания многопоточка не встанет на своё место в нашей голове никоим образом.
Итак, начнём. Организация планирования в операционной системе Windows является: гибридной. С одной стороны моделируются условия вытесняющей многозадачности, когда операционная система сама решает, когда и на основе каких условия вытеснить потоки. С другой стороны — кооперативной многозадачности, когда потоки сами решают, когда они всё сделали и можно переключаться на следующий (UMS планировщик). Режим вытесняющей многозадачности является приоритетным, т.к. решает, что будет исполняться на основе приоритетов. Почему так? Потому что у каждого потока есть свой приоритет и операционная система планирует к исполнению более приоритетные потоки. А вытесняющей потому, что если возникает более приоритетный поток, он вытесняет тот, который сейчас исполнялся. Однако во многих случаях это бы означало, что часть потоков никогда не доберется до исполнения. Поэтому в операционной системе есть много механик, позволяющих потокам, которым необходимо время на исполнение его получить несмотря на свой более низкий по сравнению с остальными, приоритет.
Уровни приоритета
Windows имеет 32 уровня приоритета (0-31)
- 1 уровень (00 — 00) — это Zero Page Thread;
- 15 уровней (01 — 15) — обычные динамические приоритеты;
- 16 уровней (16 — 31) — реального времени.
Самый низкий приоритет имеет Zero Page Thread. Это — специальный поток операционной системы, который обнуляет страницы оперативной памяти, вычищая тем самым данные, которые там находились, но более не нужны, т.к. страница была освобождена. Необходимо это по одной простой причине: когда приложение освобождает память, оно может ненароком отдать кому-то чувствительные данные. Личные данные, пароли, что-то ещё. Поэтому как операционная система так и runtime языков программирования (а у нас — .NET CLR) обнуляют получаемые участки памяти. Если операционная система понимает, что заняться особо нечем: потоки либо стоят в блокировке в ожидании чего-либо либо нет потоков, которые исполняются, то она запускает самый низко приоритетный поток: поток обнуления памяти. Если она не доберется этим потоком до каких-либо участков, не страшно: их обнулят по требованию. Когда их запросят. Но если есть время, почему бы это не сделать заранее?
Продолжая говорить о том, что к нам не относится, стоит отметить приоритеты реального времени, которые когда-то давным-давно таковыми являлись, но быстро потеряли свой статус приоритетов реального времени и от этого статуса осталось лишь название. Другими словами, Real Time приоритеты на самом деле не являются таковыми. Они являются приоритетами с исключительно высоким значением приоритета. Т.е. если операционная система будет по какой-то причине повышать приоритет потока с приоритетом из динамической группы (об этом — позже, но, например, потому, что потоку освободили блокировку) и при этом значение до повышения было равно 15
, то повысить приоритет операционная система не сможет: следующее значение равно 16
, а оно — из диапазона реального времени. Туда повышать такими вот «твиками» нельзя.
Уровень приоритетов процессов с позиции Windows API.
Приоритеты — штука относительная. И чтобы нам всем было проще в них ориентироваться, были введены некие правила относительности расчетов: во-первых все потоки вообще (от всех приложений) равны для планировщика: планировщик не различает потоки это различных приложений или же одного и того же приложения. Далее, когда программист пишет свою программу, он задаёт приоритет для различных потоков, создавая тем самым модель многопоточности внутри своего приложения. Он прекрасно знает, почему там был выбран пониженный приоритет, а тут — обычный. Внутри приложения всё настроено. Далее, поскольку есть пользователь системы, он также может выстраивать приоритеты для приложений, которые запускаются на этой системе. Например, он может выбрать повышенный приоритет для какого-то расчетного сервиса, отдавая ему тем самым максимум ресурсов. Т.е. уровень приоритета можно задать и у процесса.
Однако, изменение уровня приоритета процесса не меняет относительных приоритетов внутри приложения: их значения сдвигаются, но не меняется внутренняя модель приоритетов: внутри по-прежнему будет поток с пониженным приоритетом и поток — с обычным. Так, как этого хотел разработчик приложения. Как же это работает?
Существует 6 классов приоритетов процессов. Класс приоритетов процессов — это то, относительно чего будут создаваться приоритеты потоков. Все эти классы приоритетов можно увидеть в «Диспетчере задач», при изменении приоритета какого-либо процесса.
Другими словами класс приоритета — это то, относительно чего будут задаваться приоритеты потоков внутри приложения. Чтобы задать точку отсчёта, было введено понятие базового приоритета. Базовый приоритет — это то значение, чем будет являться приоритет потока с типом приоритета Normal:
- Если процесс создаётся с классом Normal и внутри этого процесса создаётся поток с приоритетом Normal, то его реальный приоритет Normal будет равен 8 (строка №4 в таблице);
- Если Вы создаёте процесс и у него класс приоритета Above Normal, то базовый приоритет будет равен 10. Это значит, что потоки внутри этого процесса будут создаваться с более повышенным приоритетом: Normal будет равен 10.
Для чего это необходимо? Вы как программисты знаете модель многопоточности, которая у вас присутствует.
Потоков может быть много и вы решаете, что один поток должен быть фоновым, так как он производит вычисления и вам
не столь важно, когда данные станут доступны: важно чтобы поток завершил вычисления (например поток обхода и анализа дерева). Поэтому, вы устанавливаете пониженный приоритет данного потока. Аналогично может сложится ситуация когда необходимо запустить поток с повышенным приоритетом.
Представим, что ваше приложение запускает пользователь и он решает, что ваше приложение потребляет слишком много процессорных ресурсов. Пользователь считает, что ваше приложение не столь важное в системе, как какие-нибудь другие приложения и понижает приоритет вашего приложения до Below Normal. Это означает, что он задаёт базовый приоритет 6 относительно которого будут рассчитываться приоритеты потоков внутри вашего приложения. Но в системе общий приоритет упадёт. Как при этом меняются приоритеты потоков внутри приложения?
Таблица 3
Normal остаётся на уровне +0 относительно уровня базового приоритета процесса. Below normal — это (-1) относительно уровня базового. Т.е. в нашем примере с понижением уровня приоритета процесса до класса Below Normal
приоритет потока ‘Below Normal’ пересчитается и будет не 8 - 1 = 7
(каким он был при классе Normal
), а 6 - 1 = 5
. Lowest (-2) станет равным 4
.
Idle
и Time Critical
— это уровни насыщения (-15 и +15). Почему Normal — это 0
и относительно него всего два шага: -2, -1, +1 и +2? Легко провести параллель с обучением. Мы ходим в школу, получаем оценки наших знаний (5,4,3,2,1) и нам понятно, что это за оценки: 5 — молодец, 4 — хорошо, 3 — вообще не постарался, 2 — это не делал ни чего, а 1 — это то, что можно исправить потом на 4. Но если у нас вводится 10-ти бальная система оценок (или что вообще ужас — 100-бальная), то возникает неясность: что такое 9 баллов или 7? Как понять, что вам поставили 3 или 4?
Тоже самое и с приоритетами. У нас есть Normal. Дальше, относительно Normal у нас есть чуть повыше
Normal (Normal above), чуть пониже Normal (Normal below). Также есть шаг на два вверх
или на два вниз (Higest и Lowest). Нам, поверьте, нет никакой необходимости в более подробной градации. Единственное, очень редко, может раз в жизни, нам понадобится сказать: выше чем любой приоритет в системе. Тогда мы выставляем уровень Time Critical
. Либо наоборот: это надо делать, когда во всей системе делать нечего. Тогда мы выставляем уровень Idle
. Это значения — так называемые уровни насыщения.
Как рассчитываются уровни приоритета?
У нас бал класс приоритета процесса Normal (Таблица 3) и приоритет потоков Normal — это 8. Если процесс Above Normal то поток Normal получается равен 9. Если же процесс выставлен в Higest, то поток Normal получается равен 10.
Поскольку для планировщика потоков Windows все потоки процессов равнозначны, то:
- Для процесса класса Normal и потока Above-Normal
- Для процесса класса Higest и потока Normal
конечные приоритеты будут одинаковыми и равны 10.
Если мы имеем два процесса: один с приоритетом Normal, а второй — с приоритетом Higest, но при этом
первый имел поток Higest а второй Normal, то система их приоритеты будет рассматривать как одинаковые.
Как уже обсуждалось, группа приоритетов Real-Time на самом деле не является таковой, поскольку настоящий Real-Time — это гарантированная доставка сообщения за определённое время либо обработка его получения. Т.е., другими словами, если на конкретном ядре есть такой поток, других там быть не должно. Однако это ведь не так: система может решить, что низко приоритетный поток давно не работал и дать ему время, отключив real-time. Вернее его назвать классом приоритетов который работает над обычными приоритетами и куда обычные приоритеты не могут уйти, попав под ситуации, когда Windows временно повышает им приоритет.
Но так как поток повышенным приоритетом исполняется только один на группе ядер, то получается,
что если у вас даже Real-Time потоки, не факт, что им будет выделено время.
Если перевести в графический вид, то можно заметить, что классы приоритетов пересекаются. Например, существует пересечение Above-Normal Normal Below-Normal (столбик с квадратиками):
Это значит, что для этих трех классов приоритетов процессов существуют такие приоритеты потоков внутри этих классов, что реальный приоритет будет равен. При этом, когда вы задаёте приоритет процессу вы просто повышаете или понижаете все его внутренние приоритеты потоков на определённое значение (см. Таблица 3).
Поэтому, когда процессу выдаётся более высокий класс приоритета, это повышает приоритет потоков процесса относительно обычных – с классом Normal.
Кстати говоря, мы стартовали продажи на CLRium #7, в котором мы с огромным удовольствием будем говорить про практику работы с многопоточным кодом. Будут и домашние задания и даже возможность работы с личным ментором.
Загляните к нам на сайт: мы сильно постарались, чтобы его было интересно изучить.
Процессы и потоки Windows
Внутри каждого процесса могут выполняться одна или несколько потоков, и именно поток является базовой единицей выполнения в Windows. Выполнение потоков планируется системой на основе обычных факторов: наличие таких ресурсов, как CPU и физическая память, приоритеты, равнодоступность ресурсов и так далее. Начиная с версии NT4, в Windows поддерживается симметричная многопроцессорная обработка (Symmetric Multiprocessing, SMP), позволяющая распределять выполнение потоков между отдельными процессорами, установленными в системе.
С точки зрения программиста каждому процессу принадлежат ресурсы, представленные следующими компонентами:
• Одна или несколько потоков.
• Виртуальное адресное пространство, отличное от адресных пространств других процессов, если не считать областей памяти, распределенных явным образом для совместного использования (разделения) несколькими процессами. Заметьте, что разделяемые отображенные файлы совместно используют физическую память, тогда как разделяющие их процессы используют различные виртуальные адресные пространства.
• Один или несколько сегментов кода, включая код DLL.
• Один или несколько сегментов данных, содержащих глобальные переменные.
• Строки, содержащие информацию об окружении, например, информацию о текущем пути доступа к файлам.
• Куча процесса.
• Различного рода ресурсы, например, дескрипторы открытых файлов и другие кучи.
Поток разделяет вместе с процессом код, глобальные переменные, строки окружения и другие ресурсы. Каждый поток планируется независимо от других и располагает следующими элементами:
• Стек, используемый для вызова процедур, прерываний и обработчиков исключений, а также хранения автоматических переменных.
• Локальные области хранения потока (Thread Local Storage, SLT) — массивы указателей, используя которые каждый поток может создавать собственную уникальную информационную среду.
• Аргумент в стеке, получаемый от создающего потока, который обычно является уникальным для каждого потока.
• Структура контекста, поддерживаемая ядром системы и содержащая значения машинных регистров.
На рис. 6.1 показан процесс с несколькими потоками. Рисунок является схематическим, поэтому на нем не указаны фактические адреса памяти и не соблюдены масштабы.
В данной главе показано, как работать с процессами, состоящими из единственного потока. О том, как использовать несколько потоков, рассказывается в главе 7.
Примечание
Рисунок 6.1 является высокоуровневым с точки зрения программиста представлением процесса. В действительности эта картина должна быть дополнена множеством технических деталей и особенностями реализации. Более подробную информацию заинтересованные читатели могут найти в книге Соломона (Solomon) и Руссиновича (Russinovich) Inside Windows 2000.
Процессы UNIX сопоставимы с процессами Windows, имеющими единственный поток.
Реализации UNIX недавно пополнились потоками POSIX Pthreads, которые в настоящее время используются почти повсеместно. В [40] потоки не обсуждаются; все рассмотрение основано на процессах.
Наверное, можно было бы даже не напоминать о том, что понятие потоков не является новым, и их различные реализации предлагаются поставщиками уже на протяжении целого ряда лет. Однако потоки Pthreads являются самым распространенным стандартом, в то время как коммерческие реализации потоков являются устаревшими.
Рис. 6.1. Процесс и его потоки
Читайте также
Процессы и потоки
Процессы и потоки
В этой главе представлено описание процессов и потоков в QNX/ Neutrino, диспетчеризации, системы приоритетов, и дано понятие о реальном времени. Вы узнаете о состояниях потоков и алгоритмах диспетчеризации, которые применяются в QNX/ Neutrino, а также изучите
Процессы и потоки
Процессы и потоки
Вернемся к нашим рассуждениям о потоках и процессах, но на сей раз с точки зрения перспективы их применения в системах реального времени. Затем мы рассмотрим вызовы функций, которые применяются при работе с потоками и процессами.Мы знаем, что процесс
2. Процессы и потоки
2. Процессы и потоки
При внимательном чтении технической документации [8] и литературы по ОС QNX [1] отчетливо бросается в глаза, что тонкие детали создания и функционирования процессов и потоков описаны крайне поверхностно и на весьма некачественном уровне. Возможно, это
Потоки
Потоки
Последующие расширения[14] POSIX специфицируют широкий спектр механизмов «легких процессов» — потоков (группа API pthread_*()). Техника потоков вводит новую парадигму программирования вместо уже ставших традиционными UNIX-методов. Это обстоятельство часто недооценивается.
Процессы, задачи, задания, группы активизации и потоки
Процессы, задачи, задания, группы активизации и потоки
Как уже упоминалось, первоначально в AS/400 было определено три уровня работы. Самый низкий уровень, под MI, — задача. Процесс «живет» на уровне MI и построен на структуре задач SLIC. Поверх модели процессов MI OS/400 в качестве
10.4 ПОТОКИ
10.4 ПОТОКИ
Схема реализации драйверов устройств, хотя и отвечает заложенным требованиям, страдает некоторыми недостатками, которые с годами стали заметнее. Разные драйверы имеют тенденцию дублировать свои функции, в частности драйверы, которые реализуют сетевые
1.2. Процессы, потоки и общий доступ к информации
1.2. Процессы, потоки и общий доступ к информации
В традиционной модели программирования Unix в системе могут одновременно выполняться несколько процессов, каждому из которых выделяется собственное адресное пространство. Это иллюстрирует рис. 1.1.
Рис. 1.1. Совместное
Потоки
Потоки
Хотя концепция процессов в системах Unix используется уже очень давно, возможность использовать несколько потоков внутри одного процесса появилась относительно недавно. Стандарт потоков Posix.1, называемый Pthreads, был принят в 1995 году. С точки зрения взаимодействия
38. Потоки
38. Потоки
Язык C++ не обладает средствами для ввода/вывода. Ему это и не нужно; подобные средства легко и элегантно можно создать, применяя сам язык. Стандартная библиотека потокового ввода/вывода дает возможность осуществлять гибкий и эффективный с гарантией типа метод
7.3.1.2. Потоки
7.3.1.2. Потоки
Потоки (streams) сетевого взаимодействия были разработаны Деннисом Ритчи для Unix Version 8 (1985). Их новая реализация называется STREAMS (именно так, в документации все буквы прописные). Впервые она стала доетупной в версии 3.0 System V Unix (1986). Средство STREAMS обеспечивало
7.3.1.2. Потоки
7.3.1.2. Потоки
Потоки (streams) сетевого взаимодействия были разработаны Деннисом Ритчи для Unix Version 8 (1985). Их новая реализация называется STREAMS (именно так, в документации все буквы прописные). Впервые она стала доступной в версии 3.0 System V Unix (1986). Средство STREAMS обеспечивало
2.2.1.1 Потоки
2.2.1.1 Потоки
Архитектуру INFORMIX-OnLine DS называют также многопотоковой. Для каждого клиента создается так называемый поток, или нить (thread). Поток — это подзадача, выполняемая в рамках одного из серверных процессов. В некоторых случаях для обслуживания одного клиентского
8.3 Файлы и Потоки
8.3 Файлы и Потоки
Потоки обычно связаны с файлами. Библиотека потоков содает стандартный поток ввода cin, стандартный поток вывода cout и стандартный поток ошибок cerr. Программист может отрывать другие файлы и создавать для них
ГЛABA 6 Процессы, потоки и задания
ГЛABA 6 Процессы, потоки и задания
B этой главе мы рассмотрим структуры данных и алгоритмы, связанные с процессами, потоками и заданиями в Microsoft Windows. B первом разделе основное внимание уделяется внутренним структурам данных, из которых состоит процесс. Bo втором разделе
В однозадачной ОС пользователь может запустить одновременно не более одной программы и только после того как она закончит работу можно будет запустить другую. В многозадачной ОС пользователи могут одновременно запускать несколько программ или даже несколько копий одной программы.
В чем разница между программой и процессом? Программа – это статическая последовательность команд, а процесс (process) – это программа и системные ресурсы, необходимые для ее выполнения. Процесс является субъектом владения ресурсами и единицей работы. ОС выделяет каждому процессу порцию системных ресурсов и гарантирует, что программа каждого процесса будет направляться на исполнение в определенном порядке и своевременно.
ОС содержит блок кода, управляющий созданием и удалением процессов, а также отношениями между ними. Этот код называется структурой процессов (process structure) и в Windows NT реализован диспетчером процессов (process manager).
Его основная задача предоставить набор базовых сервисов процесса, которые подсистемы среды могли бы использовать для эмуляции своих собственных уникальных структур процессов.
В разных ОС процессы реализованы по-разному. Они различаются своим представлением (структурами данных), способами именования и защиты, а также отношениями между собой. Базовые процессы Windows NT имеют ряд характеристик, отличающих их от процессов других ОС:
- процессы реализованы как объекты, и доступ к ним осуществляется посредством объектных сервисов;
- в адресном пространстве процесса может исполняться несколько потоков;
- объект-процесс и объект-поток имеют встроенные возможности синхронизации.
Что такое процесс?
Процесс состоит из:
- исполняемой программы (код и данные);
- закрытого адресного пространства (address space), т.е. набора адресов виртуальной памяти, который процесс может использовать;
- системных ресурсов, выделяемых ОС процессу во время выполнения программы (семафоров, файлов и т.д.);
- по крайней мере, одного потока управления (thread of execution). Поток – это сущность внутри процесса, которую ядро NT направляет на исполнение. Без него программа процесса не может выполняться.
Адресное пространство
С помощью системы виртуальной памяти (virtual memory) программисты (и создаваемые ими процессы) получают логический образ памяти, который не совпадает с ее физической структурой (см. Рис. 1 Виртуальная и физическая память).
При всяком обращении процесса по виртуальному адресу система виртуальной памяти транслирует этот адрес в физический адрес. Она также предотвращает непосредственный доступ процесса к виртуальной памяти, занятой другими процессами или ОС. Для исполнения кода ОС или доступа к памяти ОС поток должен выполняться в режиме ядра (kernel mode). Большинство процессов – это процессы пользовательского режима (user mode).
Поток пользовательского режима получает доступ к ОС, вызывая некоторый системный сервис. Когда поток вызывает сервис, происходит переключение из пользовательского режима в режим ядра. ОС проверяет аргументы, переданные сервису, после чего исполняет сервис. Перед возвратом управления пользовательской программе ОС переключает поток обратно в пользовательский режим.
Системные ресурсы
Кроме закрытого адресного пространства, с каждым процессом связан набор системных ресурсов.
Маркер доступа (Access Token) присоединяет к процессу ОС. Это объект исполнительной системы, который содержит информацию о правах зарегистрированного в системе пользователя, которого представляет данный процесс. Если процессу требуется получить информацию о своем маркере доступа или изменить некоторые атрибуты маркера, он должен открыть описатель своего объекта-маркера. Подсистема защиты определяет, есть ли у объекта такое право.
Ниже маркера доступа расположен набор структур данных, созданный диспетчером виртуальной памяти для отслеживания виртуальных адресов, используемых процессом.
Таблица объектов показана внизу. Процесс открыл описатели потока, файла и секции совместно используемой памяти. Описание виртуального адресного пространства содержит информацию о виртуальных адресах, занятых стеком потока и объектом-секцией.
Объект-процесс
Каждый процесс в Windows NT представлен блоком процесса, создаваемым исполнительной системой (EPROCESS). В блоке EPROCESS содержатся атрибуты процесса и указатели на некоторые структуры данных. Так, у каждого процесса есть один или более потоков, представляемых блоками потоков исполнительной системы (ETHREAD). Блок EPROCESS и связанные с ним структуры данных хранятся в системном пространстве. Исключение составляет только блок переменных окружения процесса (process environment block, PEB), он находится в адресном пространстве процесса (см. Рис. 3 Блоки переменных окружения процесса (PEB) и потока (TEB)).
В исполнительной системе процессы – это объекты исполнительной системы, создаваемые и уничтожаемые диспетчером объектов. Объект-процесс, как и другие объекты, содержит заголовок, создаваемый и инициализируемый диспетчером объектов. В заголовке хранятся стандартные атрибуты объекта, такие как дескриптор защиты объекта, имя и каталог объектов, в котором хранится имя, если оно есть.
Диспетчер процессов определяет атрибуты, хранящиеся в теле объектов-процессов, а также предоставляет системные сервисы для чтения и изменения этих атрибутов. Атрибуты и сервисы для объектов-процессов показаны на Рис. 4 Блоки процесса исполнительной системы (EPROCESS) и ядра (KPROCESS). Объект процесс исполнительной системы включает объект процесс ядра (содержит указатель на объект процесс ядра). Ядро управляет объектом процесс ядра, а исполнительная система управляет объектом исполнительной системы.
Мы рассматриваем основные атрибуты этих объектов по отдельности, для того чтобы лучше понять какие задачи решаются ядром, а какие исполнительной системой. При выполнении лабораторных работ эта детализация не существенна и надо будет использовать Win32 API функции для работы с объектом процесс исполнительной системы.
Рассмотрим основные атрибуты:
- идентификатор процесса – уникальное значение, идентифицирующее процесс в ОС;
- базовый приоритет — базовый приоритет потоков процесса;
- привязка к процессорам (процессорное сродство) – набор процессоров, на которых потоки процесса могут исполняться по умолчанию;
- размеры квот – максимальный объем резидентной и нерезидентной системной памяти, пространства в файле подкачки и процессорного времени, выделяемый пользовательскому процессу;
- статус завершения – причина завершения процесса.
Некоторые атрибуты объекта-процесса налагают ограничения на потоки, исполняемые внутри процесса. Например, на многопроцессорном компьютере процессорное сродство может ограничить исполнение потоков процесса только на заданных процессорах. Аналогично, размеры квот регулируют, сколько памяти, пространства в файле подкачки и процессорного времени могут использовать все потоки процесса вместе.
Базовый приоритет процесса помогает ядру NT регулировать приоритет потоков в системе. Приоритеты потоков изменяются, но всегда остаются в диапазоне базовых приоритетов их процессов.
Большинство сервисов объекта-процесса мы будем использовать при выполнении лабораторных работ. Например, сервис завершения процесса останавливает исполнение всех его потоков, закрывает все открытые описатели объектов и уничтожает виртуальное адресное пространство процесса.
Что такое поток?
Поток – это единица исполнения, отдельный счетчик команд или подлежащая планированию сущность внутри процесса.
В то время как процесс – это логическое представление работы, которую должна выполнить ОС, поток отображает одну из, возможно, многих необходимых подзадач. Предположим, что пользователь запустил приложение для работы с базой данных. ОС представляет этот вызов приложения как один процесс. Пусть теперь пользователь запросил генерацию отчета по данным из базы и сохранение этого отчета в файле. Пока идет выполнение этой длительной операции, пользователь ввел новый запрос к базе данных. ОС представляет каждый из запросов – генерацию отчета и новый запрос к базе – как отдельные потоки внутри процесса приложения для работы с базой данных. Эти потоки могут выполняться процессором независимо друг от друга, т.е. обе операции можно выполнять в одно и то же время (параллельно).
Основные составляющие потока в исполнительной системе NT:
- Уникальный идентификатор, называемый идентификатором клиента
- Содержимое набора регистров, отражающее состояние процессора
- Два стека: один используется потоком при работе в пользовательском режиме, а другой — в режиме ядра
- Собственная область памяти, предназначенная для использования подсистемами, библиотеками периода выполнения и динамически подключаемыми библиотеками (DLL).
Регистры, стек, и собственная область памяти называются контекстом (context) потока. Фактически данные, составляющие контекст потока, определяются типом процессора.
Поток находится в адресном пространстве процесса, используя его для хранения данных во время выполнения. Если в одном процессе существует несколько потоков, то они совместно используют адресное пространство и все ресурсы, включая маркер доступа, базовый приоритет и описатели объектов из таблицы объектов процесса. Ядро NT направляет потоки на исполнение некоторому процессору. Таким образом, каждый процесс NT должен иметь, по меньшей мере, один поток.
Многозадачность и многопроцессорная обработка
ОС вытесняющей многозадачностью должна использовать тот или иной алгоритм, позволяющий ей распределять процессорное время между потоками. Каждые 20 мс Windows просматривает все существующие объекты потоки и отмечает те из них, которые могут получить процессорное время. Далее она выбирает один из таких объектов и загружает в регистры процессора значение его контекста. Эта операция называется переключением контекста (context switching). Поток выполняет код и манипулирует данными в адресном пространстве своего процесса. Примерно через 20 мс Windows сохранит значения регистров процессора в контексте потока и приостановит его выполнение. Далее система просмотрит остальные объекты потоки, подлежащие выполнению, выберет один из них, загрузит его контекст в регистры процессора, и все повторится. Этот цикл операций – выбор потока, загрузка его контекста, выполнение и сохранение контекста – начинается с момента запуска системы и продолжается до ее выключения (см. Рис. 5 Состояния потоков).
Система планирует выполнение только тех потоков, которые могут получить процессорное время. У некоторых объектов-потоков значение счетчика простоев (suspend count) больше 0, это значит, что соответствующие потоки приостановлены и не получают процессорного времени. Кроме приостановленных, существуют и другие потоки, не участвующие в распределении процессорного времени, — они ожидают каких-либо событий.
Поток получает доступ к процессору на 20 мс, после чего планировщик переключает процессор на выполнение другого потока. Но так происходит, только если у всех потоков один приоритет. На самом деле в системе существуют потоки с разными приоритетами, а это меняет порядок распределения процессорного времени.
Каждому потоку присваивается уровень приоритета – от 0 (самый низкий) до 31 (самый высокий). Решая, какому потоку выделить процессорное время, система сначала рассматривает только потоки с приоритетом 31 и подключает их к процессору по принципу карусели. Если поток с приоритетом 31 не исключен из планирования, он получает квант времени, по истечении которого система проверяет, есть ли еще один такой поток. Если есть, то и он получает свой квант процессорного времени.
Пока в системе имеются планируемые потоки с приоритетом 31, ни один поток с более низким приоритетом процессорного времени не получит. Такая ситуация называется голоданием (starvation). Она наблюдается, когда потоки с более высоким приоритетом так интенсивно используют процессор, что остальным ничего не достается.
Кроме того, потоки с более высоким приоритетом вытесняют потоки с более низким приоритетом. Допустим, процессор исполняет поток с приоритетом 5, и тут система обнаруживает, что поток с более высоким приоритетом готов к выполнению. Тогда система остановит поток с более низким приоритетом – даже если не истек отведенный ему квант процессорного времени – подключит к процессору поток с более высоким приоритетом и выдаст ему полный квант времени.
Процессор может выполнять не более одного потока одновременно. Однако многозадачная (multitasking) ОС дает пользователю возможность исполнять несколько программ, причем создается впечатление, что все они исполняются одновременно. Это достигается следующим образом:
- поток исполняется до тех пор, пока его исполнение не будет прервано или ему не придется ждать освобождения некоторого ресурса;
- cохраняется контекст потока;
- загружается контекст другого потока;
этот цикл повторяется до тех пор, пока есть потоки, ожидающие выполнения.
Переключение процессора с исполнения одного потока на исполнение другого потока называется переключением контекста (context switching). В Windows NT оно осуществляется ядром.
Многозадачность увеличивает объем работы, выполняемой системой, потому что большинство потоков не могут исполняться непрерывно. Периодически поток прекращает выполнение и ждет пока другой поток, например, освободит ресурс необходимый первому. Благодаря многозадачности, пока один поток ожидает, может выполняться другой поток, и время процессора не расходуется впустую.
Вытесняющая многозадачность (preemptive multitasking) – это разновидность многозадачности, при которой ОС не ждет, пока поток добровольно предоставит процессор другим потокам. Вместо этого ОС прерывает поток, после того как он выполнялся в течение заранее заданного периода времени, так называемого кванта времени (time quantum), или когда готов к выполнению поток с большим приоритетом. Вытеснение предотвращает монополизацию процессора одним потоком и предоставляет другим потокам их долю процессорного времени. Windows NT – это система с вытесняющей многозадачностью. В невытесняющих системах, поток должен был добровольно передавать управление процессором. Плохие программы могут захватить процессор и нарушить работу других приложений или всей системы.
C++, WinAPI
Содержание
- Потоки (Threads) и многопоточность в Windows
- Intro
- Создаём поток
- Критические секции (Critical sections)
- Источники
Intro
ОС MS Windows 95 представила программерам идею использования многозадачной (multitasking) операционной системы (даже несмотря на то, что в действительности она таковой не являлась, т.к. использовала т.н. «вытесняющую» (preemptive) многозадачность, при которой маленькие порции множества программ выполняются, можно сказать, одновременно, в порядке одной общей очереди).1 Суть идеи заключалась в том, что на одном компьютере могло одновременно выполняться несколько процессов (приложений), каждому из которых поочерёдно выделяются определённые порции процессорного времени, называемые временные срезы (time slices).
Многозадачность также даёт возможность делить каждый процесс на несколько отдельных процессов, называемых потоками (threads). У каждого потока есть своё предназначение, например: сканирование сетевой активности, поддержка пользовательского ввода, проигрывание звуков и т.д. Использование в одном приложении более чем одного потока называется многопоточностью (multithreading).
Создаём поток
Создание отдельного потока внутри приложения — задача совсем несложная. Для создания потока ты создаёшь функцию (используя специальный прототип функции), которая содержит код, который необходимо выполнить в данном отдельном потоке. Прототип функции создания потока выглядит так:
Прототип функции ThreadProc, инициализирующей поток
DWORD WINAPI ThreadProc(LPVOID lpParameter);
Здесь аргумент lpParameter — это заданный программистом указатель, который затем необходимо будет указать при создании потока.
Создаём поток путём вызова функции CreateThread:
Прототип функции CreateThread
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAbilities, // NULL DWORD dwStackSize, // 0 LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // Указатель, предоставляемый программистом. Может быть NULL DWORD dwCreationFlags, // 0 LPDWORD lpThreadId // Получает идентификатор потока );
Обрати внимание
Возвращаемое значение функции CreateThread — это его дескриптор, который также обязательно должен быть закрыт при завершении работы потока. В противном случае ресурсы не будут освобождены. Ресурсы, занятые созданным потоком, освобождаются путём вызова функции CloseHandle:
CloseHandle(hThread); // используй дескриптор, полученный при создании потока функцией CreateThread
Следующий пример создаёт в приложении отдельный поток и инициализирует его:
// Какая-либо функция создания отдельного потока DWORD WINAPI MyThread(LPVOID lpParameter) { BOOL *Active; Active = (BOOL*)lpParameter; *Active = TRUE; // Помечаем поток как активный // Вставляем исходный код здесь // Уничтожаем поток *Active = FALSE; // Помечаем поток как неактивный ExitThread(O); // Специальный вызов функции для закрытия потока } void InitThread() { HANDLE hThread; DWORD ThreadId; BOOL Active; // Созадём поток, передавая определённую программистом переменную, которая // используется для сохранения статуса потока. hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, (void*)&Active, 0, &ThreadId); // Ожидаем завершения работы потока, путём постоянного отслеживания состояния флага активности while(Active == TRUE); // Закрываем дескриптор потока CloseHandle(hThread); }
В данном примере в приложении создаётся отдельный поток, который начинает свою работу сразу после выполнения функции CreateThread. Во время вызова данной функции мы передаём указатель в переменную типа BOOL, которая отслеживает состояние потока (активен или нет), путём установки значений в TRUE или FALSE. По завершении работы потока, выставляем флаг активности в FALSE, указывая, что поток более неактивен. Далее уничтожаем поток путём вызова функции ExitThread, имеющей всего один параметр — код уничтожения потока (termination code) (грубо говоря, причина, по которой закрывается данный поток). Обычно здесь просто указывают 0 (ноль).
По сути, поток — это обычная функция, которая выполняется параллельно со своим приложением.
Критические секции (Critical sections)
Так как все современные ОС семейства MS Windows являются многозадачными (multitasking), рано или поздно Windows-приложения начинают сбоить при обращении к общим ресурсам, особенно те, которые используют несколько потоков. Представь ситуацию, когда один поток заполняет структуру какими-либо очень важными данными, а другой вдруг пытается получить доступ к ним или даже пытается их изменить.
Существует способ, позволяющий быть уверенным, что только один выбранный поток имеет полный контроль над данными. Именно для этого был придуман механизм критических секций (областей) (critical sections).2 При активации критическая секция блокирует попытки всех остальных потоков получить доступ к разделяемой памяти (shared memory; общая память, которую используют все потоки). Это позволяет каждому отдельному потоку индивидуально получать доступ или изменять данные приложения, не опасаясь, что другие потоки смогут вмешаться в данный процесс.
Для использования критической секции её сперва необходимо объявить и инициализировать:
Инициализация критической секции
CRITICAL_SECTION CriticalSection; InitializeCriticalSection(&CriticalSection);
Сразу после этого можно войти в критическую секцию, обработать данные и выйти из неё, как это представлено ниже:
EnterCriticalSection(&CriticalSection); // Обрабатываем важные данные здесь LeaveCriticalSection(&CriticalSection);
По окончании работы с критической секцией (например при выходе из приложения), освобождаем её:
DeleteCriticalSection(&CriticalSection);
Применять критические секции совсем не трудно, но, в то же время, крайне необходимо при создании многопоточных приложений. Главное правило здесь код, расположенный в области критической секции, должен выполняться как можно быстрее. В противном случае ты рискуешь подвесить системные процессы ОС, что может привести к её «падению».