
- •Организация мультизадачности для приложений WIN32
- •ОС создает и манипулирует несколькими различными типами управляющих структур данных, называемых объектами ядра
- •Каждый раз при создании нового процесса, и, в частности, при исполнении стартового кода
- •Эта функция создает для нового процесса виртуальное адресное пространство, создает объект ядра процесс
- •Любой поток имеет возможность запустить дочерний (по отношению к процессу, владеющему данным потоком)
- •Для создания в рамках процесса нового параллельного потока используется функция CreateThread:
- •Функция потока получает управление после создания потока, адрес этой функции является параметром функции
- •В отличие от дочерних процессов, потоки, созданные в рамках одного процесса, работают в
- •Синхронизация параллельных потоков
- •Эта функция ждет освобождения (перехода в состояние signaled) заданного в качестве первого параметра
- •Более сложная синхронизация осуществляется при помощи синхронизирующих объектов ядра. Над любым синхронизирующим объектом
- •Во избежание возможных ошибок, связанных с использованием уже закрытого синхронизирующего объекта, рекомендуется при
- •параметра идентификатор объекта ядра событие. Считается, что событие произошло, если объект ядра событие
- •Примитив выхода информирует систему о том, что текущий поток вышел из критического участка.
- •CRITICAL_SECTION critsect; InitializeCriticalSection(&critsect);
- •Если счетчик содержит ненулевое значение, его значение декрементируется и потоку разрешается выполнение критического
- •Библиотеки динамической компоновки
- •обращения к функциям , необходимым параллельно работающим приложениям. При этом совместно используемые функции
- •DLL-библиотека WIN32 состоит из одной специальной функции DLLEntryPoint и произвольного набора функций, выполняющих
- •BOOL WINAPI DllEntryPoint(
- •LIBRARY mydll
- •Виртуальная память
- •Любая страница виртуального адресного пространства процесса может находиться в одном из трех состояний:
- •ретий параметр функции должен иметь значение MEM_RESERVEдля резервирования участка виртуальной памяти, значение MEM_COMMIT
- •В виртуальном адресном пространстве каждого процесса ОС резервирует участок памяти, предназначенный для использования
- •Внешняя память
- •Параметр “Режим доступа” может быть равен нулю (доступ запрещен), GENERIC_READ (доступ на чтение),
- •Внешняя память
- •Файлы, проецируемые в память
- •Имя отображаемого файла – это произвольная ASCIIZ-строка. Под этим именем отображенный файл будет
- •Другой процесс может получить доступ к файлу, отображенному в виртуальную память при помощи
- •Система команд ЭВМ. Структура машинной команды
- •5.Команды ввода и вывода информации для обмена с внешними устройствами. В некоторых ЭВМ
- •Дальнейшее упрощение команды привело к созданию одноадресных машин. Рассмотрим систему команд такой ЭВМ
- •Трехадресная команда легко расшифровывалась и была удобна в использовании, но с ростом объемов
Организация мультизадачности для приложений WIN32
С приложением в WIN32 связаны два основных понятия: процесс (process) и поток (thread). В терминах WIN32 под процессом понимается экземпляр выполняемой программы. Процесс является владельцем всех ресурсов, выделяемых операционной системой приложению для его выполнения. Процессы WIN32 инертны, то есть они не выполняются, а лишь владеют ресурсами. Последовательность выполнения команд в рамках процесса задается потоком, который рассматривается как ресурс, создаваемый процессом. В отличие от предыдущих версий Windows, в WIN32 прикладной программист имеет возможность в рамках одного процесса создать несколько параллельных потоков и обеспечить их взаимодействие и синхронизацию. Мультизадачность в Windows основана на разделении процессорного времени между отдельными потоками, а не процессами. При этом Windows поддерживает симметричную мультипроцессорную архитектуру, что позволяет не только разделять время между потоками на одном процессоре, но и запускать параллельные потоки одновременно на разных процессорах. Разделение времени между потоками и распределение потоков по процессорам выполняется ядром ОС прозрачно для программиста. С точки зрения последнего, имеется набор функций API, позволяющих создавать и синхронизировать параллельные потоки, причем принцип программирования с использованием этих функций не зависит от того, имеется ли в системе один или несколько процессоров. Таким образом, API WIN32 предоставляет достаточно широкий набор средств параллельного программирования
ОС создает и манипулирует несколькими различными типами управляющих структур данных, называемых объектами ядра (kernelobjects). К объектам ядра относятся процессы (process objects), потоки (thread objects), файлы (file objects), семафоры (semaphore objects), события (event objects), объекты взаимоисключения (mutex objects), каналы (pipe objects), почтовые ящики (mailslotobjects) и некоторые другие. Назначение некоторых из перечисленных типов объектов ядра мы рассмотрим немного позже. Для создания объекта каждого типа используются соответствующие функции API. Когда объект ядра больше не требуется, его обязательно следует закрыть при помощи универсальной функции CloseHandle. Каждый объект ядра может находиться в одном из двух состояний: свободном (signaled) и занятом (nonsignaled). При помощи специальных функций API поток может быть переведен в состояние ожидания до тех пор, пока не освободится (перейдет в состояние signaled) некоторый объект ядра, что широко используется для синхронизации параллельных потоков. Кроме того, каждый объект ядра имеет счетчик числа пользователей. При создании объекта ядра значение счетчика равно 1. Если при помощи соответствующей функции API поток получает идентификатор объекта ядра типа HANDLE, значение счетчика увеличивается на 1. Функция CloseHandle уменьшает значение счетчика, но объект ядра удаляется ею из системы только если значение счетчика равно нулю (закрыт последний идентификатор объекта ядра (handle), связанный с объектом).
Каждый раз при создании нового процесса, и, в частности, при исполнении стартового кода функции WinMain, вызывается функция API WIN32 CreateProcess:
BOOL CreateProcess( LPCTSTR lpApplicationName, // имя запускаемого модуля (exe- файла)
LPTSTR lpCommandLine, // указатель на строку с параметрами запуска LPSECURITY_ATTRIBUTES lpProcessAttributes, // указатель на атрибуты безопасности
// процесса LPSECURITY_ATTRIBUTES lpThreadAttributes, // указатель на атрибуты безопасности
// первичного потока 71 BOOL bInheritHandles, // флаг наследования идентификаторов процесса и потока DWORD dwCreationFlags, // флаги создания процесса
LPVOID lpEnvironment, // указатель на блок окружения процесса LPCTSTR lpCurrentDirectory, // путь к текущему каталогу LPSTARTUPINFO lpStartupInfo, // указатель на структуру STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation // указатель на структуру
// PROCESS_INFORMATION );
Эта функция создает для нового процесса виртуальное адресное пространство, создает объект ядра процесс с начальным значением счетчика, равным единице, переводит объект ядра в состояние nonsignaled и вызывает функцию CreateThread для создания первичного потока, соответствующего коду функции WinMain. В случае успешного создания нового процесса функция CreateProccess возвращает значение TRUE.
Атрибуты безопасности задаются структурой SECURITY_ATRIBUTES и содержат дескриптор безопасности для создаваемого процесса и его первичного потока. Рекомендуется в качестве указателя на эту структуру в параметрах рассматриваемой функции использовать NULL. В этом случае атрибуты безопасности автоматически определяются системой. Флаг наследования определяет, будет ли новый процесс наследовать идентификаторы родительского процесса и его первичного потока. Флаги создания процесса воздействуют на способ создания и приоритет нового процесса. В частности, имеется возможность создать процесс и сразу его заблокировать, отложив инициализацию первичного потока. Затем его можно будет возобновить при помощи функции ResumeThread. Можно также запустить процесс в режиме отладки, что обычно используется отладчиками, запускающими отлаживаемую программу в качестве дочернего процесса. Четыре флага (IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS и REALTIME_PRIORITY_CLASS) задают класс приоритета нового процесса. Самый низкий класс приоритета IDLE, самый высокий – REALTIME, прикладные процессы обычно имеют класс приоритета NORMAL. Структура STARTUPINFO определяет вид главного окна нового процесса после запуска. В структуре PROCESS_INFORMATION ОС возвращает идентификаторы нового процесса и первичного потока, а также идентификаторы объектов ядра процесс и поток типа HANDLE. Более подробную информацию можно получить из справочной документации или электронного
справочника WIN32 Programmer’s Reference.
Любой поток имеет возможность запустить дочерний (по отношению к процессу, владеющему данным потоком) процесс, вызвав функцию CreateProcess. При этом можно сразу оборвать связь между дочерним и родительским процессами, вызвав функцию CloseHandle для идентификаторов объектов ядра поток и процесс, возвращаемых в структуре PROCCESS_INFORMATION, или отложить эту операцию для обеспечения взаимодействия между дочерним и родительским процессом. В любом случае, названную операцию необходимо выполнить до завершения родительского процесса, так как в противном случае система не сможет удалить объекты ядра процесс и поток для родительского процесса (их счетчики не будут равны нулю).
Процесс завершается, когда один из его потоков вызывает функцию ExitProcess. Эта функция также автоматически вызывается кодом завершения функции WinMain. При завершении процесса прекращается выполнение всех его потоков, освобождаются ресурсы, выделенные процессу, объект ядра процесс переводится в состояние signaled, уменьшается на единицу счетчик числа его пользователей, и если счетчик равен нулю объект ядра процесс освобождается. Завершение родительского процесса не приводит к завершению порожденных им дочерних процессов. В API WIN32 имеется также функция TerminateProcess, которая позволяет завершить любой процесс, идентификатор которого передается ей в качестве параметра.
Для создания в рамках процесса нового параллельного потока используется функция CreateThread:
72
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // указатель на атрибуты безопасности DWORD dwStackSize, //начальный размер стека потока в байтах LPTHREAD_START_ROUTINE lpStartAddress, //адрес функции потока
LPVOID lpParameter, // параметр функции потока DWORD dwCreationFlags, // флаги создания потока
LPDWORD lpThreadId //адрес переменной для возвращаемого идентификатора потока ); Функция возвращает идентификатор вновь созданного объекта ядра поток типа HANDLE или NULL при ошибке. Кроме того, через последний параметр функции возвращается идентификатор самого потока. Атрибуты безопасности имеют тот же смысл, что и для функции CreateProcess. В качестве начального размера стека можно указать нуль, тогда используется размер стека родительского потока. В качестве флагов создания потока можно указывать либо нуль, тогда выполнение потока начнется немедленно после создания, либо константу CREATE_SUSPENDED. В последнем случае поток будет создан, но его выполнение будет отложено. Возобновить выполнение потока можно при помощи функции ResumeThread. Приостановить выполнение потока можно и в ходе его выполнения при помощи функции SuspendThread.
Перед созданием потока в приложении должна быть определена функция потока, имеющая следующий прототип (название функции может быть любым): DWORD ThreadFunc(LPVOID lpParameter);
Функция потока получает управление после создания потока, адрес этой функции является параметром функции CreateThread. Параметром функции потока является произвольное 32-разрядное значение, передаваемое при создании потока через соответствующий параметр функции CreateThread. Обычно этот параметр является указателем на некоторый объект и должен быть приведен к типу LPVOID. При нормальном завершении функция потока должна вернуть нулевое значение. На выполняемые функцией потока операции не накладывается никаких ограничений, однако, если функция потока создает окно и намеревается обрабатывать сообщения для этого окна, то она должна иметь собственный цикл обработки сообщений. Более часто, параллельные потоки создаются для выполнения длительных вычислительных операций. Функции таких потоков обычно не имеют цикла
обработки сообщений.
Для каждого потока в системе определен квант времени, в течение которого ему принадлежит процессор. В системе реализована стратегия приоритетного планирования, согласно которой любой поток имеет приоритет от 0 до 31, и очередной квант времени выделяется потоку с максимальным приоритетом. Менее привилегированные потоки получают процессор только в том случае, если более приоритетные потоки завершаются или переходят в состояние ожидания (блокируются). Сразу после создания потока он имеет приоритет, равный классу приоритета процесса, создавшего поток (4-для IDLE, 7-9 - для NORMAL, 13 – для HIGH, 24 – для REALTIME). При помощи функции SetThreadPriority поток имеет возможность изменить свой приоритет относительно первоначального.
Второй параметр этой функции задает относительное изменение приоритета: THREAD_PRIORITY_HIGHEST (+2), THREAD_PRIORITY_ABOVE_NORMAL (+1), THREAD_PRIORITY_NORMAL (0), THREAD_PRIORITY_BELOW_NORMAL (-1), THREAD_PRIORITY_LOWEST (-2). Параметр THREAD_PRIORITY_IDLE позволяет установить для потока приоритет равный 16 (для процессов с классом приоритета REALTIME) или 1 (для процессов с другим классом приоритета). Аналогичным образом, параметр THREAD_PRIORITY_TIME_CRITICAL позволяет установить для потока приоритет равный 31 (для процессов с классом приоритета REALTIME) или 15 (для процессов с другим классом приоритета). Функция GetThreadPriority позволяет определить текущий приоритет потока. Кроме того, функция SetPriorityClass позволяет установить класс приоритета процесса, а функция GetPriorityClass– определить текущий класс приоритета процесса.
73
Для завершения потока используется функция ExitThread. Эта функция также автоматически вызывается при завершении функции потока. При завершении потока объект ядра поток переводится в состояние signaled, уменьшается на единицу счетчик числа его пользователей, и если счетчик равен нулю объект ядра поток освобождается. Если завершаемый поток является последним активным потоком процесса, то завершается и сам процесс. В API WIN32 имеется также функция TerminateThread, которая позволяет завершить любой процесс, идентификатор которого передается ей в качестве параметра.
В отличие от дочерних процессов, потоки, созданные в рамках одного процесса, работают в общем адресном пространстве и могут сравнительно легко взаимодействовать друг с другом и пользоваться общими данными. Как правило, дочерние процессы порождаются для выполнения сложных задач, когда имеет смысл максимально “обособить” дочерний процесс от родительского. Процессы функционируют в отдельных адресных пространствах и для передачи данных между процессами используются достаточно сложные механизмы, например, проецирование файлов в память. Потоки служат обычно для распараллеливания небольших вычислительных задач в рамках одного процесса. При этом следует отметить, что использование параллельных потоков должно быть оправдано, так как, чем больше потоков в системе, тем выше непроизводительные затраты времени на диспетчеризацию потоков.
Синхронизация параллельных потоков
Как правило, наибольшую трудность при параллельном программировании представляет задача синхронизации асинхронных параллельных потоков. Обычно синхронизация состоит в том, что некоторому потоку или потокам приходится ожидать какого-то события, связанного с другими потоками. Так, если несколько потоков разделяют общий ресурс, как правило, нельзя допускать одновременного обращения потоков к данному ресурсу. Программисту в этом случае следует организовать взаимоисключение потоков. В качестве примера можно привести доступ параллельных потоков, формирующих изображение в одном окне, к соответствующему контексту отображения.
API WIN32 предоставляет программисту широкий набор средств для решения задачи синхронизации параллельных потоков. Основную роль в синхронизации потоков играет функция WaitForSingleObject:
DWORD WaitForSingleObject(
HANDLE hObject, |
// идентификатор ожидаемого объекта ядра |
DWORD dwTimeout |
// интервал ожидания в миллисекундах ); |