Ниже представлена не простая расшифровка доклада с семинара 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, в котором мы с огромным удовольствием будем говорить про практику работы с многопоточным кодом. Будут и домашние задания и даже возможность работы с личным ментором.
Загляните к нам на сайт: мы сильно постарались, чтобы его было интересно изучить.
2024-12-03
Введение
Управление процессами в Windows API (WinAPI) включает в себя создание, управление и взаимодействие с процессами и потоками в операционной системе Windows. Ниже приведены основные функции и концепции, используемые для работы с процессами в WinAPI:
- Создание процессов:
CreateProcess
: Функция, которая создает новый процесс. Вы можете указать исполняемый файл, командную строку и другие параметры при создании процесса.
- Информация о процессе:
GetCurrentProcess
: Функция, которая возвращает дескриптор текущего процесса.OpenProcess
: Функция, которая открывает существующий процесс по его идентификатору (PID).GetProcessId
: Функция, которая возвращает идентификатор процесса для данного дескриптора процесса.
- Управление приоритетом процессов:
SetPriorityClass
: Функция, которая устанавливает приоритет выполнения процесса.GetPriorityClass
: Функция, которая возвращает текущий приоритет процесса.
- Управление потоками внутри процесса:
CreateThread
: Функция, которая создает новый поток внутри процесса.TerminateThread
: Функция, которая завершает выполнение потока.
- Синхронизация между потоками и процессами:
Mutex
,Semaphore
,Event
: Различные объекты синхронизации, которые можно использовать для синхронизации работы процессов и потоков.
- Завершение процессов:
ExitProcess
: Функция, которая завершает текущий процесс.TerminateProcess
: Функция, которая принудительно завершает другой процесс.
- Получение информации о процессах:
EnumProcesses
: Функция, которая позволяет перечислить все запущенные процессы и получить их идентификаторы.QueryFullProcessImageName
: Функция, которая возвращает путь к исполняемому файлу процесса по его идентификатору.
- Модули процесса:
EnumProcessModules
: Функция, которая позволяет перечислить все модули (библиотеки DLL) внутри процесса.
- Управление разрешениями и защитой процессов:
OpenProcessToken
,GetTokenInformation
: Функции для работы с маркерами доступа процессов и получения информации о правах доступа.
Создание процессов
Создание процессов в операционной системе Windows с использованием Windows API (WinAPI) осуществляется с помощью функции CreateProcess
. Эта функция позволяет запустить новый процесс и настроить различные атрибуты его выполнения. Вот пример использования функции CreateProcess
на языке C++:
#include <windows.h>
#include <tchar.h>
int main() {
SetConsoleOutputCP(1251);
// Имя исполняемого файла, который нужно запустить
LPCTSTR applicationName = _T("C:\\Path\\To\\Your\\Program.exe");
// Командная строка для передачи процессу
LPTSTR commandLine = NULL;
// Защитные атрибуты процесса и потока (обычно NULL)
LPSECURITY_ATTRIBUTES processAttributes = NULL;
LPSECURITY_ATTRIBUTES threadAttributes = NULL;
// Флаги создания процесса
BOOL inheritHandles = FALSE;
DWORD creationFlags = 0;
LPVOID environment = NULL;
LPCTSTR currentDirectory = NULL;
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInfo;
// Заполнение структуры STARTUPINFO
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
startupInfo.cb = sizeof(STARTUPINFO);
// Создание нового процесса
BOOL result = CreateProcess(
applicationName, // Имя исполняемого файла
commandLine, // Командная строка
processAttributes, // Атрибуты процесса
threadAttributes, // Атрибуты потока
inheritHandles, // Флаг наследования дескрипторов
creationFlags, // Флаги создания процесса
environment, // Переменные окружения (обычно NULL)
currentDirectory, // Текущий рабочий каталог (обычно NULL)
&startupInfo, // Структура STARTUPINFO
&processInfo // Структура PROCESS_INFORMATION
);
if (result) {
// Процесс успешно создан
// Вы можете получить информацию о процессе, используя processInfo
// Закрыть дескрипторы, чтобы избежать утечек ресурсов
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
} else {
// Произошла ошибка при создании процесса
DWORD error = GetLastError();
// Обработка ошибки
}
return 0;
}
Обратите внимание, что вы должны заменить C:\\Path\\To\\Your\\Program.exe
на путь к исполняемому файлу, который вы хотите запустить. Функция CreateProcess
возвращает TRUE
, если процесс успешно создан, и FALSE
, если произошла ошибка. В случае ошибки, вы можете использовать GetLastError
для получения кода ошибки и дальнейшей обработки.
Также обратите внимание на необходимость закрыть дескрипторы процесса и потока с помощью CloseHandle
после того, как они не нужны, чтобы избежать утечек ресурсов.
Функция CreateProcess
в Windows API имеет множество параметров для настройки создания нового процесса. Вот описание основных параметров:
lpApplicationName
(тип:LPCTSTR
):- Имя исполняемого файла, который нужно запустить. Этот параметр может быть NULL, если имя исполняемого файла включено в строку командной строки.
lpCommandLine
(тип:LPTSTR
):- Командная строка, передаваемая запускаемому процессу. Этот параметр может содержать имя исполняемого файла и его аргументы. Если
lpApplicationName
не является NULL, тоlpCommandLine
будет использоваться для передачи аргументов командной строки. В противном случае,lpCommandLine
должен содержать полную команду.
- Командная строка, передаваемая запускаемому процессу. Этот параметр может содержать имя исполняемого файла и его аргументы. Если
lpProcessAttributes
(тип:LPSECURITY_ATTRIBUTES
):- Атрибуты безопасности процесса. Обычно устанавливаются в NULL для использования атрибутов по умолчанию.
lpThreadAttributes
(тип:LPSECURITY_ATTRIBUTES
):- Атрибуты безопасности потока. Обычно устанавливаются в NULL для использования атрибутов по умолчанию.
bInheritHandles
(тип:BOOL
):- Флаг, указывающий, должны ли дескрипторы открытых файлов и другие ресурсы наследоваться процессом, созданным функцией
CreateProcess
.
- Флаг, указывающий, должны ли дескрипторы открытых файлов и другие ресурсы наследоваться процессом, созданным функцией
dwCreationFlags
(тип:DWORD
):- Флаги создания процесса, определяющие различные параметры и поведение процесса. Например, вы можете использовать
CREATE_NEW_CONSOLE
, чтобы создать новое окно консоли для процесса.
- Флаги создания процесса, определяющие различные параметры и поведение процесса. Например, вы можете использовать
lpEnvironment
(тип:LPVOID
):- Указатель на блок переменных окружения, которые будут использоваться в новом процессе. Обычно устанавливается в NULL, чтобы процесс унаследовал текущее окружение.
lpCurrentDirectory
(тип:LPCTSTR
):- Текущий рабочий каталог для нового процесса. Обычно устанавливается в NULL, чтобы процесс использовал текущий рабочий каталог родительского процесса.
lpStartupInfo
(тип:LPSTARTUPINFO
):- Указатель на структуру STARTUPINFO, которая содержит информацию о создаваемом процессе, такую как дескрипторы для ввода, вывода и ошибок.
lpProcessInformation
(тип:LPPROCESS_INFORMATION
):- Указатель на структуру PROCESS_INFORMATION, в которой будут возвращены дескрипторы процесса и потока после успешного создания процесса.
Информация о процессе
Для получения информации о процессе в операционной системе Windows с помощью Windows API (WinAPI) можно использовать несколько функций и структур данных. Вот основные средства для получения информации о процессе:
OpenProcess
:- Функция
OpenProcess
позволяет открыть существующий процесс и получить дескриптор процесса (HANDLE), который можно использовать для выполнения различных операций над процессом. Эта функция принимает в качестве параметров идентификатор процесса (PID) и права доступа.
- Функция
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
GetProcessId
:- Функция
GetProcessId
используется для получения идентификатора процесса (PID) по его дескриптору.
- Функция
DWORD processId = GetProcessId(hProcess);
GetProcessTimes
:- Функция
GetProcessTimes
позволяет получить информацию о времени выполнения процесса, включая время начала выполнения и использования ЦП.
- Функция
FILETIME creationTime, exitTime, kernelTime, userTime;
if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
// Обработка информации о времени выполнения процесса
}
GetProcessMemoryInfo
:- Функция
GetProcessMemoryInfo
позволяет получить информацию о потреблении памяти процессом.
- Функция
PROCESS_MEMORY_COUNTERS memInfo;
if (GetProcessMemoryInfo(hProcess, &memInfo, sizeof(memInfo))) {
// Обработка информации о потреблении памяти процессом
}
QueryFullProcessImageName
:- Функция
QueryFullProcessImageName
используется для получения полного пути к исполняемому файлу процесса.
- Функция
TCHAR processPath[MAX_PATH];
DWORD pathSize = sizeof(processPath) / sizeof(TCHAR);
if (QueryFullProcessImageName(hProcess, 0, processPath, &pathSize)) {
// Обработка пути к исполняемому файлу процесса
}
CloseHandle
:- Для освобождения ресурсов, связанных с дескриптором процесса, используйте функцию
CloseHandle
.
- Для освобождения ресурсов, связанных с дескриптором процесса, используйте функцию
Обратите внимание, что в приведенных примерах hProcess
— это дескриптор открытого процесса, а processId
— идентификатор процесса. Важно учесть, что для успешного использования этих функций требуются соответствующие права доступа к процессу, и некоторые из них могут вернуть ошибки при попытке получения информации о некоторых процессах из-за ограничений безопасности.
Управление приоритетом процессов
Управление приоритетом процессов в операционной системе Windows можно выполнять с помощью Windows API (WinAPI). Приоритет процесса определяет, как операционная система распределяет процессорное время между процессами. Вот основные функции и концепции, связанные с управлением приоритетом процессов:
-
Установка приоритета процесса:
Для установки приоритета процесса используется функция
SetPriorityClass
. Эта функция изменяет приоритет выполнения всего процесса.BOOL success = SetPriorityClass(GetCurrentProcess(), priority);
Здесь
priority
может принимать одно из следующих значений:HIGH_PRIORITY_CLASS
: Высокий приоритет.NORMAL_PRIORITY_CLASS
: Нормальный приоритет (по умолчанию).IDLE_PRIORITY_CLASS
: Низкий приоритет.REALTIME_PRIORITY_CLASS
: Реальное время (осторожно при использовании, так как это может привести к зависанию системы).
-
Получение текущего приоритета процесса:
Для получения текущего приоритета процесса используется функция
GetPriorityClass
.DWORD priority = GetPriorityClass(GetCurrentProcess());
Значение
priority
будет одним из перечисленных выше констант. -
Установка приоритета потока:
Внутри процесса можно устанавливать приоритет для отдельных потоков с помощью функции
SetThreadPriority
. Это позволяет управлять приоритетами выполнения различных задач внутри одного процесса.BOOL success = SetThreadPriority(hThread, priority);
Здесь
hThread
— дескриптор потока, аpriority
— желаемый приоритет потока. -
Получение текущего приоритета потока:
Для получения текущего приоритета потока используется функция
GetThreadPriority
.int priority = GetThreadPriority(hThread);
Значение
priority
будет числовым представлением приоритета потока.
Обратите внимание, что изменение приоритетов процессов и потоков должно выполняться осторожно, так как неправильное управление приоритетами может повлиять на производительность системы. Особенно следует быть осторожным при изменении приоритета процессов в режиме реального времени, так как это может привести к проблемам с отзывчивостью системы.
Управление потоками внутри процесса
Управление потоками внутри процесса в операционной системе Windows можно выполнять с использованием Windows API (WinAPI). Вот основные функции и концепции, связанные с управлением потоками:
-
Создание потока:
Для создания нового потока внутри процесса используется функция
CreateThread
. Эта функция позволяет запустить новый поток и выполнить в нем определенную функцию.HANDLE hThread = CreateThread( NULL, // Атрибуты безопасности потока (обычно NULL) 0, // Размер стека (0 = размер стека по умолчанию) ThreadFunction, // Функция, которая будет выполнена в потоке lpParam, // Дополнительные параметры для функции 0, // Флаги создания потока (0 = запуск сразу после создания) &dwThreadId // Идентификатор потока );
Здесь
ThreadFunction
— это указатель на функцию, которая будет выполняться в потоке, иlpParam
— дополнительные параметры, которые могут быть переданы в функцию. -
Завершение потока:
Для завершения выполнения потока используется функция
ExitThread
. Вызов этой функции приведет к завершению текущего потока. -
Ожидание завершения потока:
Для ожидания завершения выполнения потока используется функция
WaitForSingleObject
илиWaitForMultipleObjects
, в зависимости от количества потоков, которые нужно ожидать.DWORD dwExitCode; DWORD dwWaitResult = WaitForSingleObject(hThread, INFINITE); if (dwWaitResult == WAIT_OBJECT_0) { // Поток завершил выполнение GetExitCodeThread(hThread, &dwExitCode); // Обработка результата выполнения потока (dwExitCode) } else { // Обработка ошибки ожидания }
-
Установка приоритета потока:
Установка приоритета выполнения потока внутри процесса выполняется с помощью функции
SetThreadPriority
.BOOL success = SetThreadPriority(hThread, priority);
Здесь
hThread
— дескриптор потока, аpriority
— желаемый приоритет потока. -
Получение текущего приоритета потока:
Для получения текущего приоритета потока используется функция
GetThreadPriority
.int priority = GetThreadPriority(hThread);
-
Закрытие дескриптора потока:
После завершения работы с потоком, его дескриптор должен быть закрыт с помощью функции
CloseHandle
.
Обратите внимание, что управление потоками требует аккуратности, так как неправильное использование потоков может привести к различным проблемам, таким как гонки данных и блокировки. Важно правильно синхронизировать доступ к общим ресурсам, к которым имеют доступ несколько потоков в вашем приложении.
Синхронизация между потоками и процессами
Синхронизация между потоками и процессами является важной частью многозадачных приложений, которые работают в операционной системе Windows. Она позволяет управлять доступом к общим ресурсам, обеспечивать правильный порядок выполнения задач и избегать гонок данных. Вот основные механизмы синхронизации, доступные в Windows API:
-
Критические секции (Critical Sections):
- Критические секции предоставляют простой и легковесный способ синхронизации между потоками внутри одного процесса.
- Функции для работы с критическими секциями включают
InitializeCriticalSection
,EnterCriticalSection
,LeaveCriticalSection
, иDeleteCriticalSection
.
-
Мьютексы (Mutexes):
- Мьютексы могут использоваться для синхронизации между разными процессами, а не только потоками внутри одного процесса.
- Функции для работы с мьютексами включают
CreateMutex
,WaitForSingleObject
, иReleaseMutex
.
-
Семафоры (Semaphores):
- Семафоры позволяют контролировать доступ к ресурсам, когда требуется счетчик.
- Функции для работы с семафорами включают
CreateSemaphore
,WaitForSingleObject
, иReleaseSemaphore
.
-
События (Events):
- События используются для уведомления одного или нескольких потоков или процессов о возникновении события.
- Функции для работы с событиями включают
CreateEvent
,SetEvent
,WaitForSingleObject
, и другие.
-
Критические ресурсы и мьютексы файла (File Mapping and File Mutexes):
- Эти механизмы позволяют синхронизировать доступ к разделяемым данным, которые находятся в памяти или на диске.
- Самые распространенные функции включают
CreateFileMapping
,MapViewOfFile
, иWaitForSingleObject
.
-
Readers-Writers Locks:
- Эти механизмы синхронизации позволяют определять правила доступа для читающих и записывающих потоков к общим данным.
- Функции для работы с Reader-Writer Locks включают
InitializeSRWLock
,AcquireSRWLockExclusive
, иAcquireSRWLockShared
.
-
События завершения (Completion Events):
- Эти события используются для уведомления о завершении выполнения асинхронных операций ввода-вывода.
- Функции для работы с событиями завершения включают
CreateIoCompletionPort
,GetQueuedCompletionStatus
, и другие.
Выбор подходящего механизма синхронизации зависит от конкретных требований вашего приложения и структуры данных. Важно правильно использовать синхронизацию, чтобы избежать проблем с гонками данных, блокировками и ожиданиями.
Завершение процессов
В Windows API (WinAPI) есть несколько способов завершения процессов, включая нормальное завершение и принудительное завершение. Вот основные функции и методы для завершения процессов:
-
ExitProcess:
- Функция
ExitProcess
используется для нормального завершения текущего процесса. Это завершает выполнение текущей программы и завершает процесс.
Где
exitCode
— код завершения процесса, который будет возвращен операционной системой. - Функция
-
TerminateProcess:
- Функция
TerminateProcess
используется для принудительного завершения другого процесса. Это позволяет завершить процесс, даже если он не отвечает или заблокирован.
BOOL success = TerminateProcess(hProcess, exitCode);
Где
hProcess
— дескриптор процесса, который нужно завершить, иexitCode
— код завершения процесса. - Функция
-
WM_CLOSE и PostMessage:
- Если вы хотите завершить приложение с графическим интерфейсом, вы можете отправить сообщение
WM_CLOSE
главному окну приложения с помощью функцииPostMessage
. Это позволит приложению выполнить закрытие как обычно.
PostMessage(hWnd, WM_CLOSE, 0, 0);
Где
hWnd
— дескриптор главного окна приложения. - Если вы хотите завершить приложение с графическим интерфейсом, вы можете отправить сообщение
Обратите внимание, что при использовании функции TerminateProcess
процесс завершается немедленно и не выполняет никаких завершающих операций, что может привести к утечкам ресурсов и несохранению данных. По возможности рекомендуется использовать нормальное завершение процессов с помощью ExitProcess
или закрытие окон с использованием WM_CLOSE
и PostMessage
, чтобы приложение могло выполнить необходимые действия перед завершением.
Получение информации о процессах
Для получения информации о процессах в операционной системе Windows можно использовать Windows API. Существует несколько функций и структур данных, которые позволяют получить информацию о текущих работающих процессах. Вот пример получения списка процессов и их атрибутов:
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <tchar.h>
int main() {
SetConsoleOutputCP(1251);
// Создаем объект, представляющий снимок всех процессов
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
// Обработка ошибки
return 1;
}
// Структура, в которую будет сохранен атрибут процесса
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
// Получаем информацию о первом процессе в снимке
if (!Process32First(hProcessSnap, &pe32)) {
CloseHandle(hProcessSnap);
// Обработка ошибки
return 1;
}
// Перебираем все процессы в снимке
do {
_tprintf(_T("Процесс ID: %d, Имя: %s\n"), pe32.th32ProcessID, pe32.szExeFile);
// Здесь можно получать и обрабатывать другие атрибуты процесса
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return 0;
}
Обратите внимание на следующие ключевые моменты в этом коде:
-
CreateToolhelp32Snapshot
создает снимок (snapshot) всех процессов в системе. -
Process32First
используется для получения информации о первом процессе в снимке, аProcess32Next
для перебора всех остальных процессов. -
PROCESSENTRY32
— это структура, которая хранит атрибуты процесса, такие как идентификатор процесса (PID) и имя исполняемого файла. -
Информация о процессе выводится на экран с помощью
_tprintf
, но ее можно использовать для любой нужной обработки.
Обратите внимание, что для успешной компиляции и выполнения этого кода необходимо включить библиотеку kernel32.lib
и указать верное символическое имя или путь к исполняемому файлу.
Модули процесса
Для получения информации о модулях (библиотеках и исполняемых файлах) внутри процесса в операционной системе Windows, вы можете использовать Windows API. Основным инструментом для этой задачи является функция EnumProcessModules
. Вот пример использования:
#include <windows.h>
#include <psapi.h>
#include <tchar.h>
#include <stdio.h>
int main() {
SetConsoleOutputCP(1251);
DWORD processId = GetCurrentProcessId(); // Идентификатор текущего процесса
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (hProcess == NULL) {
// Обработка ошибки открытия процесса
return 1;
}
HMODULE hModules[1024];
DWORD cbNeeded;
// Получаем список модулей внутри процесса
if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) {
for (DWORD i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
TCHAR szModName[MAX_PATH];
// Получаем имя модуля
if (GetModuleFileNameEx(hProcess, hModules[i], szModName, sizeof(szModName) / sizeof(TCHAR))) {
_tprintf(_T("Модуль #%u: %s\n"), i, szModName);
}
}
}
CloseHandle(hProcess);
return 0;
}
В этом примере мы:
-
Получаем идентификатор текущего процесса с помощью
GetCurrentProcessId
. -
Открываем процесс с помощью
OpenProcess
, чтобы получить дескриптор процесса с правами на чтение информации о модулях. -
Используем
EnumProcessModules
для получения списка модулей внутри процесса.EnumProcessModules
возвращает массив дескрипторов модулей. -
Для каждого модуля мы используем
GetModuleFileNameEx
, чтобы получить полный путь к модулю и выводим его имя.
Не забудьте включить библиотеку psapi.lib
при компиляции и убедитесь, что код выполняется с правами, позволяющими открывать процессы и читать их модули.
Управление разрешениями и защитой процессов
Функции OpenProcessToken
и GetTokenInformation
в Windows API используются для получения информации о безопасности и разрешениях, связанных с процессом. Давайте рассмотрим их использование подробнее:
-
OpenProcessToken:
Функция
OpenProcessToken
используется для открытия дескриптора безопасности (токена) процесса. Этот токен содержит информацию о безопасности, такую как SID (идентификатор безопасности) пользователя и группы, разрешения и другие атрибуты безопасности процесса. Вот как можно использоватьOpenProcessToken
:#include <windows.h> #include <tchar.h> int main() { SetConsoleOutputCP(1251); DWORD processId = ...; // Идентификатор процесса, для которого нужно получить токен HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId); if (hProcess == NULL) { // Обработка ошибки return 1; } HANDLE hToken; if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { // Теперь у вас есть дескриптор токена (hToken) для процесса, // который вы можете использовать для получения информации о безопасности. // Закрываем дескриптор токена после использования. CloseHandle(hToken); } CloseHandle(hProcess); return 0; }
-
GetTokenInformation:
После открытия дескриптора токена с помощью
OpenProcessToken
, вы можете использовать функциюGetTokenInformation
, чтобы получить различные атрибуты и информацию о безопасности. Вы должны предоставить буфер для хранения информации. Вот пример получения информации о SID (идентификаторе безопасности) пользователя из токена:HANDLE hToken = ...; // Дескриптор токена DWORD dwSize = 0; // Получаем размер буфера, необходимый для информации о SID. GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize); // Выделяем буфер и получаем информацию о SID. PTOKEN_USER pTokenUser = (PTOKEN_USER)malloc(dwSize); if (GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize)) { // Теперь у вас есть информация о SID пользователя. // Освобождаем выделенный буфер после использования. free(pTokenUser); } // Закрываем дескриптор токена. CloseHandle(hToken);
Использование критических секций
Критические секции (Critical Sections) — это механизм синхронизации в Windows API, который позволяет защитить доступ к общим данным от одновременного доступа нескольких потоков внутри одного процесса. Они обычно используются для предотвращения гонок данных и обеспечения корректного доступа к разделяемым ресурсам. Вот пример использования критических секций:
#include <windows.h>
// Объявляем глобальную критическую секцию
CRITICAL_SECTION g_criticalSection;
// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// Входим в критическую секцию
EnterCriticalSection(&g_criticalSection);
// Здесь можно выполнять операции с общими данными
// Выходим из критической секции
LeaveCriticalSection(&g_criticalSection);
return 0;
}
int main() {
SetConsoleOutputCP(1251);
// Инициализируем критическую секцию
InitializeCriticalSection(&g_criticalSection);
// Создаем потоки, которые будут использовать критическую секцию
HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
// Ожидаем завершения потоков
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
// Закрываем дескрипторы потоков
CloseHandle(hThread1);
CloseHandle(hThread2);
// Уничтожаем критическую секцию
DeleteCriticalSection(&g_criticalSection);
return 0;
}
Обратите внимание на следующие моменты:
-
Мы используем
InitializeCriticalSection
для инициализации критической секции иDeleteCriticalSection
для ее удаления после использования. -
В функции
ThreadFunction
перед доступом к общим данным мы входим в критическую секцию с помощьюEnterCriticalSection
, а после завершения операций с общими данными мы выходим из нее с помощьюLeaveCriticalSection
. -
Потоки создаются с помощью
CreateThread
, и каждый из них выполняетThreadFunction
. -
Мы ожидаем завершения выполнения потоков с помощью
WaitForSingleObject
.
Использование критических секций обычно предпочтительнее внутри одного процесса, так как они более эффективны и проще в использовании по сравнению с другими механизмами синхронизации, такими как мьютексы и семафоры. Критические секции обеспечивают внутреннюю синхронизацию в пределах одного процесса и не подходят для синхронизации между разными процессами.
Использование мьютексов
Мьютексы (Mutexes) — это механизм синхронизации в Windows API, который используется для управления доступом к разделяемым ресурсам, чтобы предотвратить гонки данных между несколькими потоками или процессами. Мьютексы обычно используются для синхронизации между потоками в разных процессах. Вот пример использования мьютексов для синхронизации между двумя потоками:
#include <windows.h>
#include <stdio.h>
// Объявляем глобальный мьютекс
HANDLE g_mutex;
// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// Попытка захвата мьютекса
DWORD dwWaitResult = WaitForSingleObject(g_mutex, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0) {
// Мьютекс успешно захвачен
// Здесь можно выполнять операции с разделяемыми ресурсами
// Освобождение мьютекса
ReleaseMutex(g_mutex);
} else {
// Обработка ошибки
}
return 0;
}
int main() {
SetConsoleOutputCP(1251);
// Создаем мьютекс
g_mutex = CreateMutex(NULL, FALSE, NULL);
if (g_mutex == NULL) {
// Обработка ошибки создания мьютекса
return 1;
}
// Создаем два потока
HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
// Ожидаем завершения потоков
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
// Закрываем дескрипторы потоков
CloseHandle(hThread1);
CloseHandle(hThread2);
// Закрываем дескриптор мьютекса
CloseHandle(g_mutex);
return 0;
}
Обратите внимание на следующие моменты:
-
Мы используем
CreateMutex
для создания мьютекса иCloseHandle
для его закрытия после использования. -
В функции
ThreadFunction
мы используемWaitForSingleObject
для попытки захвата мьютекса иReleaseMutex
для его освобождения после завершения операций с разделяемыми ресурсами. -
Мьютекс может быть захвачен только одним потоком или процессом одновременно. Если другой поток или процесс попытается захватить мьютекс, пока он уже занят, то он будет блокирован до освобождения мьютекса текущим владельцем.
Это простой пример использования мьютексов для синхронизации между двумя потоками. Мьютексы также могут использоваться для синхронизации между разными процессами, если они используют один и тот же мьютекс с именем, доступным для обоих процессов.
Использование семафоров
Семафоры (Semaphores) — это ещё один механизм синхронизации в Windows API, который используется для контроля доступа к разделяемым ресурсам между несколькими потоками или процессами. Семафоры могут позволить нескольким потокам одновременно получить доступ к общему ресурсу в ограниченном количестве. Вот пример использования семафора для синхронизации между несколькими потоками:
#include <windows.h>
#include <stdio.h>
// Объявляем глобальный семафор и устанавливаем начальное значение
HANDLE g_semaphore;
// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// Попытка уменьшить счетчик семафора
DWORD dwWaitResult = WaitForSingleObject(g_semaphore, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0) {
// Семафор успешно уменьшен
// Здесь можно выполнять операции с разделяемыми ресурсами
// Увеличение счетчика семафора
ReleaseSemaphore(g_semaphore, 1, NULL);
} else {
// Обработка ошибки
}
return 0;
}
int main() {
SetConsoleOutputCP(1251);
// Создаем семафор с начальным счетчиком
g_semaphore = CreateSemaphore(NULL, 2, 2, NULL); // В данном примере, начальный счетчик равен 2
if (g_semaphore == NULL) {
// Обработка ошибки создания семафора
return 1;
}
// Создаем два потока
HANDLE hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
// Ожидаем завершения потоков
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
// Закрываем дескрипторы потоков
CloseHandle(hThread1);
CloseHandle(hThread2);
// Закрываем дескриптор семафора
CloseHandle(g_semaphore);
return 0;
}
Обратите внимание на следующие моменты:
-
Мы используем
CreateSemaphore
для создания семафора иCloseHandle
для его закрытия после использования. Начальное значение счетчика семафора в данном примере равно 2, что позволяет двум потокам одновременно получить доступ к разделяемым ресурсам. -
В функции
ThreadFunction
мы используемWaitForSingleObject
для попытки уменьшения счетчика семафора иReleaseSemaphore
для его увеличения после завершения операций с разделяемыми ресурсами. -
Семафор может позволить одновременно получить доступ только указанному количеству потоков (в данном случае, двум потокам). Если больше потоков попытаются одновременно уменьшить счетчик семафора, они будут блокированы до его освобождения другими потоками.
Семафоры предоставляют мощный механизм для управления доступом к разделяемым ресурсам в многопоточных и многопроцессных приложениях.
Использование событий
События (Events) — это механизм синхронизации в Windows API, который используется для уведомления одного или нескольких потоков о наступлении определенного события. События могут быть использованы для синхронизации между потоками или процессами, когда один поток ждет, пока другой поток или процесс оповестит его о наступлении события. Вот пример использования событий:
#include <windows.h>
#include <stdio.h>
// Объявляем глобальное событие
HANDLE g_event;
// Функция, выполняемая в потоках
DWORD WINAPI ThreadFunction(LPVOID lpParam) {
// Ожидание события
DWORD dwWaitResult = WaitForSingleObject(g_event, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0) {
// Событие успешно сработало
// Здесь можно выполнять действия, связанные с событием
printf("Событие сработало в потоке.\n");
} else {
// Обработка ошибки
}
return 0;
}
int main() {
SetConsoleOutputCP(1251);
// Создаем событие
g_event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_event == NULL) {
// Обработка ошибки создания события
return 1;
}
// Создаем поток
HANDLE hThread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
// Задержка для демонстрации события
Sleep(2000);
// Устанавливаем событие
SetEvent(g_event);
// Ожидаем завершения потока
WaitForSingleObject(hThread, INFINITE);
// Закрываем дескриптор потока
CloseHandle(hThread);
// Закрываем дескриптор события
CloseHandle(g_event);
return 0;
}
Обратите внимание на следующие моменты:
-
Мы используем
CreateEvent
для создания события иCloseHandle
для его закрытия после использования. В данном примере событие создается в несигнальном состоянии (сигнал равен FALSE), что означает, что ожидающие потоки или процессы будут блокированы до тех пор, пока событие не будет установлено. -
В функции
ThreadFunction
мы используемWaitForSingleObject
для ожидания события. Когда событие сработает (будет установлено), поток продолжит выполнение и выполнит действия, связанные с событием. -
В основной функции, после создания потока, мы устанавливаем событие с помощью
SetEvent
. Это приводит к тому, что ожидающий потокThreadFunction
сразу же продолжает выполнение.
События могут быть использованы для синхронизации и сигнализации между потоками или процессами, и они часто используются в многозадачных и многопроцессных приложениях для организации совместной работы потоков.
Система управления
виртуальной памятью контролирует
распределение памяти и работу страничной
организации. Диспетчер памяти может
использовать страницы размером от 4 до
64 кб. Каждый пользовательский процесс
получает отдельное 32-битное адресное
пространство, предоставляющее процессу
4 Гб памяти (диапазон адресов 232).
Часть памяти зарезервирована под ОС
так, что каждому пользователю доступны
только 2 Гб виртуального адресного
пространства.
В страничной
организации каждая страница может
находится в следующих состояниях:
-
доступна, т.е.
страница используется процессом; -
зарезервирована
– множество страниц, которые диспетчер
отвел под процессы, но которые не
учитываются в квоте памяти процесса
до их использования; -
размещена –
страница, для которой диспетчер выделяет
память в файле подкачки страниц на
диске, в котором располагаются страницы
при удалении их из основной памяти.
Диспетчер памяти
управляет резидентным множеством
страниц каждого процесса, загруженного
в основную память. При первой активации
процессу передается несколько кадров
основной памяти, не весь новый процесс
загружается в основную память. Когда
процесс обращается к странице,
отсутствующей в основной памяти, одна
из страниц этого процесса выгружается
в файл подкачки страниц, а на ее место
загружается в зависимости от приоритета,
другая информация (страница).
При выполнении
процесса (потока) ОС должна принимать
3 типа решений, связанных с планированием:
долгосрочное, среднее и краткосрочное
(рис.24). Долгосрочное определяет, когда
новый процесс должен поступить в систему.
Среднесрочное является частью свопинга
и определяет, какой из готовых к выполнению
процессов будет выполняться процессором
следующим.
При
краткосрочном планировании планировщик
использует ряд критериев. Основные:
время отклика системы (для пользователя)
и степень использования процессора
(для системы) [3]. Они противоречивы.
Имеется ряд альтернативных критериев
для выбора среди готовых к выполнению
потоков.
-
Первым поступил
– первым обслужен. Выбирается поток,
ожидающий обслуживания дольше других. -
Круговое
планирование. Использует кванты времени
для ограничения времени непрерывного
выполнения потока (они выполняются
циклически). -
Выбор самого
короткого потока. Выбирается поток с
минимальным времени работы. Вытеснение
потоков не применяется. -
Наименьшее
остающееся время. Выбирается поток с
минимальным ожидаемым временем
оставшейся работы. Поток может быть
вытеснен.
Рис.24. Уровни
планирования загрузки процессора
-
Наивысшее отношению
отклика. Принимаемое решение опирается
на оценку нормализованного времени
оборота потока по состояниям. -
Снижение
приоритета. Определяет множество
очередей и распределяет в них потоки,
основываясь на истории выполнения и
других критериях.
Выбор алгоритмов
или их комбинации зависит от ожидаемой
производительности и сложности
реализации.
В Windows NT реализован
планировщик с вытеснением и гибкой
системой уровней приоритетов, включая
круговое планирование на каждом уровне,
а для уровней «переменных приоритетов»
– динамическое изменение приоритета
на основе текущей активности потоков.
В Windows NT две группы (класса) приоритетов:
реального времени и переменные, по 16
уровней в каждом (рис. 25). При появлении
потока (в состоянии готов) с большим
приоритетом, чем у выполняющегося
потока, выполняющийся поток вытесняется
и начинает выполняться поток с большим
приоритетом.
Рис. 25 Приоритеты
потоков в Windows
В классе РВ все
потоки имеют ориентированные приоритеты
от 16 до 31, которые никогда не изменяется.
Все активные потоки с определенным
уровнем приоритета располагаются в
круговой очереди данного уровня.
Приоритеты потоков в период исполнения
остаются постоянными [3].
В классе переменных
приоритетов поток начинает работу с
некоторым изначально заданным приоритетом,
который затем может измениться в большую
или меньшую сторону в зависимости от
деятельности потоков. Таким образом,
на каждом уровне приоритета имеется
своя очередь, но потоки могут переходить
из одной очереди в другую в пределах
класса переменных приоритетов.
Начальный приоритет
потока в классе переменных приоритетов
определяется двумя величинами: базовым
приоритетом процесса и базовым приоритетом
потока (рис.26).
Одним из атрибутов
процесса является его базовый приоритет,
который может принимать значения от 0
до 15. Каждый объект потока, связанный с
объектом процесса, имеет собственный
базовый приоритет, который указывает
базовый приоритет потока по отношению
к приоритету процесса. Он может отличаться
от базового приоритета процесса не
более чем на 2 уровня в большую или
меньшую сторону. Так, например, если
базовый приоритет процесса равен 4, а
базовый приоритет одного из его потоков
равен -1, то начальный приоритет (при
создании потока) этого потока равен 3.
15 |
|||||
14 |
|||||
13 |
|||||
12 |
|||||
11 |
|||||
10 |
|||||
9 |
|||||
8 |
|||||
7 |
|||||
6 |
Наивысший |
||||
5 |
Повышенный |
||||
4 |
Базовый приоритет |
Обычный |
|||
3 |
Пониженный |
Начальный |
|||
2 |
Наинизший |
||||
1 |
|||||
0 |
|||||
Приоритет процесса |
Базовый приоритет |
Динамический приоритет |
Рис.
26 Пример отношения приоритетов в Windows
NT
После активизации
потока из класса переменных приоритетов
его действительный приоритет (именуемый
динамическим приоритетом потока) может
колебаться в определенных пределах. Он
не может упасть ниже наименьшего базового
приоритета потока и не может подняться
выше максимально возможного значения
приоритета для данного класса, т.е. 15. В
приведенном выше примере (рис.26) это
диапазон приоритетов от 2 до 15.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
1. Системное программирование. Процессы и потоки в Windows API
2. Процессы и потоки в Windows API
Процесс (задача) – исполняемое компьютером
приложение вместе со всеми ресурсами, которые
требуются для его исполнения
Контекст процесса – все ресурсы процесса
Ресурсы процесса:
Адресное
пространство – виртуальная память,
выделенная процессу для запуска программ
Один или несколько потоков
Дескрипторы, кучи, стек, сегменты данных и кода и т.п.
Поток управления (thread) – последовательность
выполнения инструкций программы процессором
Поток – основная единица выполнения в Windows
Различные исходные данные порождают
различные потоки управления
3. Управление потоками в Windows API
Программы могут быть:
Однопоточные
Многопоточные
Потоки в приложении:
Главный (первичный) поток – функция main или WinMain
Другие потоки создаются явно – функции специального
вида (callback, функции обратного вызова)
Функция для создания потока имеет вид:
DWORD WINAPI <имя>(LPVOID lpParameters)
Требования к функции для создания потоков:
Реентерабельность – не использует неблокированные
глобальные и статические переменные, не возвращает
указатели на статические данные, не изменяет свой код
Безопасность для потоков – блокирует доступ к
используемым ей ресурсам
4. Управление потоками в Windows API
Состояния потока:
Блокирован – программа не готова, процессор не
выделен
Готов – программа готова, процессор не выделен
Выполняется – программа готова, процессор выделен
Новый — поток еще не начавший свою работу
Завершен — поток, завершивший свою работу
Приостановлен (подвешен) — сам поток или другой
поток приостановил его выполнение
Спит — поток приостановил свое исполнение на
некоторый интервал времени
Переход между состояниями осуществляется:
Самим или другими потоками
Операционной системой
5. Управление потоками в Windows API
6. Управление потоками в Windows API
Распределением квантов времени процессора в ОС
занимается менеджер потоков:
Сохраняет контекст прерываемого потока
Восстанавливает контекст выполняемого потока на
момент его прерывания
Передает управление запускаемому потоку
Создание потоков:
Функция CreateThread
Макрокоманда _beginthreadex из <process.h>
Завершение потока:
Функция ExitThread – вызывает сам поток
Функция TerminateThread – из других потоков
Возврат значения из функции потока через return
Макрокоманда _endthreadex из <process.h>
7. Управление потоками в Windows API
Получение кода завершения потока:
Функция GetExitCodeThread
Приостановка и возобновление потоков:
Функция SuspendThread – приостановить поток
Функция ResumeThread – возобновить выполнение
потока
Функция Sleep – задержать исполнение данного потока
Получение псевдодескриптора текущего потока:
Функция GetCurrentThread
Обработка ошибок
Функция GetLastError – поддерживается отдельно для
каждого потока
Функция SetLastError – установить код ошибки
Функция FormatMessage – текстовое сообщение ошибки
8. Управление потоками в Windows API
Использование стандартных библиотек Си:
Стандартные библиотеки Си не поддерживают
многопоточные приложения – не все функции этих
библиотек являются реентерабельными
Microsoft C предлагает реализацию стандартных
библиотек (CRT), поддерживающих многопоточность:
LIBCMT.LIB и MSVCRT.LIB
Для включения многопоточной версии стандартных
библиотек C необходимо (для Visual Studio):
В свойствах проекта указать Runtime Library: Multi-threaded
или добавить директиву #define _MT перед включением
библиотек с помощью #include
Подключить <process.h>
Вместо функций CreateThread и ExitThread использовать
_beginthreadex и _endthreadex
Допускается использование return для завершения потока
9. Управление потоками в Windows API
Собственные (защищенные) области памяти для
потоков:
Данные в стеке: данные, получаемые через параметр
при создании потока, и автоматическая память
Локальные области хранения потоков (TLS):
Функция TlsAlloc – получить индекс в TLS
Функция TlsFree – освободить индекс в TLS
Функция TlsGetValue – получить значение в TLS
Функция TlsSetValue – установить значение в TLS
Управление приоритетом потоков:
Функция GetThreadPriority – получить приоритет
Функция SetThreadPriority – установить приоритет
Функция потока может передаваться при
создании потока с приведением типа к
LPTHREAD_START_ROUTINE
10. Управление процессами в Windows API
Создание нового процесса:
Функция BOOL CreateProcess(
LPCTSTR lpApplicationName, //имя исполняемого модуля
LPTSTR lpCommandLine, //командная строка
LPSECURITY_ATTRIBUTES lpsaProcess, //защита процесса
LPSECURITY_ATTRIBUTES lpsaThread, //защита потока
BOOL bInheritHandles, //наследование дескрипторов
DWORD dwCreationFlags, //флаги создания процесса
LPVOID lpEnvironment, //параметры среды окружения
LPCTSTR lpCurDir, //текущий каталог
LPSTARTUPINFO lpStartupInfo, //вид основного окна
LPPROCESS_INFORMATION lpProcInfo //дескрипторы и ID
);
Некоторые возможные значения dwCreationFlags:
CREATE_SUSPENDED – приостановленное состояние
DETACHED_PROCESS – новый процесс без консоли
CREATE_NEW_CONSOLE – своя консоль у нового процесса
NORMAL_PRIORITY_CLASS и др. – приоритет нового процесса
11. Управление процессами в Windows API
Создание нового процесса:
Структура lpStartupInfo – информация об окне нового
процесса и о дескрипторах стандартных устройств в/в:
cb – размер структуры; обычно sizeof(STARTUPINFO)
lpTitle – заголовок окна консоли (не для
GUI-приложений)
hStdInput – дескриптор стандартного устройства ввода
hStdOutput – дескриптор стандартного устройства вывода
hStdError – дескриптор стандартного устройства ошибок
dwFlags – флаги, указывающие на реально используемые
поля структуры (STARTF_USESTDHANDLES для использования
дескрипторов стандартных устройств) и др.
Получить структуру из текущего процесса: GetStartupInfo
Структура lpProcInfo – информация о дескрипторах и
идентификаторах нового процесса и потока:
hProcess, hThread –
дескрипторы нового процесса и потока
dwProcessId, dwThreadId – ID нового процесса и потока
12. Управление процессами в Windows API
Создание нового процесса:
Имя исполняемого файла процесса указывается через:
lpApplicationName – полный путь
к приложению
lpCommandLine – через командную строку (могут
указываться параметры при запуске)
Если процесс запускается через командную строку
(относительный путь), то исполняемый файл ищется в:
1.
2.
3.
4.
5.
Каталоге модуля текущего процесса
Текущем каталоге
Системном каталоге Windows
Каталоге Windows
Каталогах, перечисленных в переменной окружения PATH
Управление блоком параметров окружения (параметр
lpEnvironment) – функции:
GetEnvironmentVariable – получить переменные
SetEnvironmentVariable – установить переменные
13. Управление процессами в Windows API
Наследуемые дескрипторы объектов:
Процессы, запускаемый из другого – процесс-потомок
(дочерний) для исходного родительского процесса
Процессы-потомки могут наследовать дескрипторы
объектов родительского процесса, кроме:
Дескрипторов виртуальной памяти
Дескрипторов DLL
Для наследования дескрипторов необходимо:
Сделать нужные дескрипторы наследуемыми
Установить bInheritHandles = TRUE при создании процесса
Передать наследуемые дескрипторы дочернему процессу
Сделать дескриптор наследуемым можно:
Через атрибуты безопасности при создании объекта
С помощью функции SetHandleInformation
Через дублирование дескриптора – DuplicateHandle
14. Управление процессами в Windows API
Наследуемые дескрипторы объектов:
Дублирование дескрипторов используется когда
необходимо изменить свойства дескриптора,
управляющие доступом к объекту (помимо свойства
наследования)
Передача дескриптора наследуемому процессу:
Через параметр командной строки
Через дескрипторы стандартных устройств ввода/вывода
Через средства межпроцессного взаимодействия (IPC)
Функции для работы с процессами:
Завершение процессов:
– завершение текущего процесса
TerminateProcess – завершение процесса из другого
процесса
GetExitCodeProcess – получение кода завершения
ExitProcess
15. Управление процессами в Windows API
Функции для работы с процессами:
Получение информации о себе:
– псевдодескриптор процесса
GetCurrentProcessId – идентификатор процесса
Получение нормального дескриптора текущего процесса –
либо через дублирование псевдодескриптора, либо с
помощью функции OpenProcess
GetCurrentProcess
Получение количества открытых дескрипторов
текущего процесса:
Функция GetProcessHandleCount
Изменение приоритетов процесса и его потоков:
GetPriorityClass – получение приоритета процесса
SetPriorityClass – установка приоритета процесса
SetProcessPriorityBoost – установка режима
динамического изменения базового приоритета потоков
GetProcessPriorityBoost – получение режима (да/нет)
16. Блокировка потоков в Windows API
Функции блокировки выполнения потоков:
Перечисленные ниже функции ожидают перехода
объекта синхронизации в сигнальное состояние
Для процессов и потоков сигнальное состояние
наступает после завершения процесса/потока
Текущий поток (где вызывается такая функция)
блокируется (приостанавливается) до тех пор, пока:
Объект синхронизации не перейдет в сигнальное состояние
Не истечет время ожидания (таймаут)
DWORD WaitForSingleObject(
HANDLE hObject, //дескриптор объекта синхронизации
DWORD dwMilliseconds);
Некоторые возможные результаты:
WAIT_OBJECT_0 – объект перешел в сигнальное состояние
WAIT_TIMEOUT – наступил таймаут (время ожидания истекло)
WAIT_FAILED – ошибка
17. Блокировка потоков в Windows API
Функции блокировки выполнения потоков:
DWORD WaitForMultipleObjects(
DWORD nCount, //количество объектов синхронизации
CONST HANDLE *lpHandles, //массив дескрипторов
BOOL fWaitAll, //режим ожидания
DWORD dwMilliseconds //таймаут (интервал ожидания)
);
Некоторые возможные результаты:
От WAIT_OBJECT_0 до (WAIT_OBJECT_0 + nCount — 1) –
интерпретация зависит от значения fWaitAll
WAIT_TIMEOUT – наступил таймаут (время ожидания истекло)
WAIT_FAILED – ошибка
Возможные значение для параметра dwMilliseconds:
Таймаут в миллисекундах
INFINITE – бесконечное ожидание
18. Управление потоками в Windows API
Задание 6:
Самостоятельно изучить функции Windows API:
CreateThread
ExitThread
Написать программу для параллельного поиска букв
«a» и «b» в строке (подсчитать их количество):
1.
2.
3.
4.
5.
Ввести строку (до создания второго потока)
Создать второй поток для поиска букв «a»
В главном потоке выполнить поиск букв «b»
Подождать завершение второго потока
Закрыть дескриптор второго потока
Строку объявить как TCHAR * volatile str (для Си) или
описать в main и передать второму потоку как параметр
Включить многопоточную реализацию CRT (для Си)
Можно использовать любую среду разработки и любой
тип приложения (GUI или консольное)
19. Управление потоками в Windows API
Задание 7:
Самостоятельно изучить функции Windows API:
SuspendThread
ResumeThread
Sleep
TerminateThread
Написать программу для управления вторым потоком
из главного потока:
1. Создать второй поток, который через каждую секунду должен
выводить следующее натуральное число
2. Завершается второй поток либо из главного, либо когда
выведутся 20 чисел. При завершении вывести сообщение
3. В главном потоке вывести меню (в цикле) для управления
вторым потоком (приостановить, возобновить, завершить)
Включить многопоточную реализацию CRT (для Си)
Можно использовать любую среду разработки и любой
тип приложения (GUI или консольное)
20. Управление процессами в Windows API
Задание 8:
Самостоятельно изучить функции Windows API:
GetCurrentProcess
DuplicateHandle
TerminateProcess
GetExitCodeThread
Написать программу для завершения потока первого
процесса во втором процессе:
1. Создать новый поток (выводит числа через 1 сек. на консоль)
2. Продублировать дескриптор нового потока с режимом
доступа THREAD_TERMINATE и с возможностью наследования
3. Запустить второй процесс со своей консолью, передать ему
дублированный дескриптор через командную строку
4. Второй процесс выводит меню, где предлагает завершить
поток первого процесса или выйти из программы
5. Дождаться завершения второго процесса в первом,
завершить второй поток (если нужно), закрыть дескрипторы
Любая среда разработки, консольные приложения
21. Блокировка потоков в Windows API
Задание 9:
Написать программу для ожидания завершения
любого из двух потоков:
1. Создать два новых потока. Первый поток выводит 5
положительных чисел, второй – 5 отрицательных. После
вывода каждого числа каждый поток засыпает на случайное
количество миллисекунд (не более 2000).
2. В главном потоке дождаться завершения любого из двух
новых потоков
3. После завершения ожидания вывести номер завершившегося
потока и завершить второй поток, если он еще не завершился
4. Закрыть дескрипторы потоков
Можно использовать любую среду разработки и любой
тип приложения (GUI или консольное)
22. Спасибо за внимание.
Управление потоками
Управление потоками
Вероятно, вы не будете удивлены, узнав о том, что у потоков, как и у любого другого объекта Windows, имеются дескрипторы и что для создания потоков, выполняющихся в адресном пространстве вызывающего процесса, предусмотрен системный вызов CreateThread. Как и в случае процессов, мы будем говорить иногда о «родительских» и «дочерних» потоках, хотя ОС не делает в этом отношении никаких различий. Системный вызов CreateThread предъявляет ряд специфических требований:
• Укажите начальный адрес потока в коде процесса.
• Укажите размер стека, и необходимое пространство стека будет выделено из виртуального адресного пространства процесса. Размер стека по умолчанию равен размеру стека основного потока (обычно 1 Мбайт). Первоначально для стека отводится одна страница (см. главу 5). Новые страницы стека выделяются по мере надобности до тех пор, пока стек не достигнет своего максимального размера, поэтому не сможет больше расти.
• Задайте указатель на аргумент, передаваемый потоку. Этот аргумент может быть чем угодно и должен интерпретироваться самим потоком.
• Функция возвращает значение идентификатора (ID) и дескриптор потока.
В случае ошибки возвращаемое значение равно NULL.
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpsa, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddr, LPVOID lpThreadParm, DWORD dwCreationFlags, LPDWORD lpThreadId)
Параметры
lpsa — указатель на уже хорошо знакомую структуру атрибутов защиты.
dwStackSize — размер стека нового потока в байтах. Значению 0 этого параметра соответствует размер стека по умолчанию, равный размеру стека основного потока.
lpStartAddr — указатель на функцию (принадлежащую контексту процесса), которая должна выполняться. Эта функция принимает единственный аргумент в виде указателя и возвращает 32-битовый код завершения. Этот аргумент может интерпретироваться потоком либо как переменная типа DWORD, либо как указатель. Функция потока (ThreadFunc) имеет следующую сигнатуру:
DWORD WINAPI ThreadFunc(LPVOID)
lpThreadParm — указатель, передаваемый потоку в качестве аргумента, который обычно интерпретируется потоком как указатель на структуру аргумента.
dwCreationFlags — если значение этого параметра установлено равным 0, то поток запускается сразу же после вызова функции CreateThread. Установка значения CREATE_SUSPENDED приведет к запуску потока в приостановленном состоянии, из которого поток может быть переведен в состояние готовности путем вызова функции ResumeThread.
lpThreadId — указатель на переменную типа DWORD, которая получает идентификатор нового потока; в Windows 9x и Windows NT 3.51 значение NULL для этого параметра устанавливать нельзя.
Любой поток процесса может сама завершить свое выполнение, вызвав функцию ExitThread, однако более обычным способом самостоятельного завершения потока является возврата из функции потока с использованием кода завершения в качестве возвращаемого значения. По завершении выполнения потока память, занимаемая ее стеком, освобождается. В случае если поток был создан в библиотеке DLL, будет вызвана соответствующая точка входа DllMain (глава 4) с указанием флага DLL_THREAD_DETACH в качестве «причины» этого вызова.
VOID ExitThread(DWORD dwExitCode)
Когда завершается выполнение последнего потока, завершается и выполнение самого процесса.
Выполнение потока также может быть завершено другим потоком с помощью функции TerminateThread, однако освобождения ресурсов потока при этом не происходит, обработчики завершения не выполняются и уведомления библиотекам DLL не посылаются. Лучше всего, когда поток сам завершает свое выполнение; применять для этого функцию TerminateThread крайне нежелательно. Функции TerminateThread присущи те же недостатки, что и функции TerminateProcess.
Поток, выполнение которого было завершено (напомним, что обычно поток должен самостоятельно завершать свое выполнение), продолжает существовать до тех пор, пока посредством функции CloseHandle не будет закрыт ее последний дескриптор. Любой другой поток, возможно и такой, который ожидает завершения другого потока, может получить код завершения потока.
BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode)
lpExitCode — будет содержать код завершения потока, указывающий на его состояние. Если поток еще не завершен, значение этой переменной будет равно STILL_ACTIVE.
Читайте также
Дополнительные функции управления потоками
Дополнительные функции управления потоками
Несмотря на то что функций управления потоками, которые мы выше обсуждали, вполне достаточно для большинства случаев, в том числе и для примеров, приведенных в этой книге, в Windows XP и Windows Server 2003 были введены две дополнительные
Дросселирование семафора для уменьшения состязательности между потоками
Дросселирование семафора для уменьшения состязательности между потоками
Слишком большое количество потоков, соревнующихся между собой за право владения единственным ресурсом, например, мьютексом или объектом CS, могут стать причиной снижения производительности как в
26.2. Основные функции для работы с потоками: создание и завершение потоков
26.2. Основные функции для работы с потоками: создание и завершение потоков
В этом разделе мы рассматриваем пять основных функций для работы с потоками, а в следующих двух разделах мы используем эти функции для написания потоковой модификации клиента и сервера
8.4. Управление процессами
8.4. Управление процессами
Первым делом научимся определять, какие процессы в системе запущены. Для этого в Linux (как и во всех UNIX-системах) имеется команда ps. Если ее запустить без всяких параметров, то она выдает список процессов, запущенных в текущей сессии. Если вы хотите
8.5. Управление пользователями
8.5. Управление пользователями
Задача управления пользователями имеет большое значение для истинно многопользовательских систем. Для персонального компьютера, о котором идет речь в этой книге, эта задача не так актуальна. Тем не менее, некоторые вопросы отразить
8.6. Управление ресурсами
8.6. Управление ресурсами
В этом разделе мы рассмотрим только один аспект управления ресурсами: как сэкономить тот или иной ресурс, точнее, как поступить в случае, если какого-то ресурса недостаточно. Основными ресурсами компьютера являются память и дисковое пространство.
4.15.3. Управление стримером
4.15.3. Управление стримером
Управление стримером выполняет программа int. Она входит в состав пакета mt-st, который обычно входит в состав дистрибутива. Эта программа точно есть в дистрибутивах Red Hat и Mandrake Linux. Программа mt использует устройство /dev/nftape, которое является ссылкой
5.8. Управление протоколированием
5.8. Управление протоколированием
Этот раздел посвящен демону syslogd, а также управлению протоколированием сообщений системы и ядра с помощью этого демона. Прежде всего следует отметить, что демон находится в пакете sysklogd (если вы, конечно, используете Red Hat-совместимую
15.5.3. Управление кэшем
15.5.3. Управление кэшем
cache_swap_high числоПри достижении этого уровня заполнения кэша (в процентном соотношении) начинается ускоренный процесс удаления старых объектов. cache_swap_low 90Процесс удаления прекращается при достижении этого уровня. maximum_object_size 4096 KBМаксимальный размер
18.5.3. Управление кэшем
18.5.3. Управление кэшем
За управление кэшем отвечают следующие директивы:? cache_mem <число> — задает размер оперативной памяти, отводимой под кэш. Размер этот указывается в байтах, килобайтах, мегабайтах (MB) или гигабайтах (GB). По умолчанию используется значение 8 MB;? cache_dir
Автоматическое управление потоками сервера
Автоматическое управление потоками сервера
Чтобы посмотреть, как осуществляется управление потоками сервера, добавим в процедуру сервера команду выдачи ее идентификатора потока. Добавим в нее также пятисекундную паузу, чтобы имитировать длительное выполнение. За это
Автоматическое управление потоками сервера: несколько процедур
Автоматическое управление потоками сервера: несколько процедур
В предыдущем примере процесс-сервер содержал лишь одну процедуру сервера. Вопрос, которым мы займемся теперь, звучит так: могут ли несколько процедур одного процесса использовать один и тот же пул потоков
Б.2. Основные функции для работы с потоками: создание и завершение
Б.2. Основные функции для работы с потоками: создание и завершение
В этом разделе мы опишем пять основных функций для работы с потоками.Функция pthread_createПри запуске пpoгрaммы вызовом exec создается единственный поток, называемый начальным потоком, или главным (initial thread).
4.1.3. Значения, возвращаемые потоками
4.1.3. Значения, возвращаемые потоками
Если второй аргумент функции pthread_join() не равен NULL, то в него помещается значение, возвращаемое потоком. Как и потоковый аргумент, это значение имеет тип void*. Если поток возвращает обычное число типа int, его можно свободно привести к типу