Программа 6.6. jobmgt: получение идентификатора процесса по номеру задачи

DWORD FindProcessId(DWORD JobNumber)

/* Получить ID процесса для задачи с указанным номером. */

{

HANDLE hJobData;

JM_JOB JobRecord;

DWORD nXfer;

TCHAR JobMgtFileName[MAX_PATH];

OVERLAPPED RegionStart;

/* Открыть файл управления задачами. */

GetJobMgtFileName(JobMgtFileName);

hJobData = CreateFile(JobMgtFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (hJobData == INVALID_HANDLE_VALUE) return 0;

/* Перейти к позиции записи, соответствующей указанному номеру задачи.

* В полной версии программы обеспечивается принадлежность номера задачи (JobNumber) допустимому диапазону значений. */

SetFilePointer(hJobData, SJM_JOB * (JobNumber – 1), NULL, FILE_BEGIN);

/* Блокировка и чтение записи. */

RegionStart.Offset = SJM_JOB * (JobNumber – 1);

RegionStart.OffsetHigh =0; /* Предполагаем, что файл короткий. */

RegionStart.hEvent = (HANDLE)0;

LockFileEx(hJobData, 0, 0, SJM_JOB,0, RegionStart);

ReadFile(hJobData, JobRecord, SJM_JOB, nXfer, NULL);

UnlockFileEx(hJobData, 0, SJM_JOB, 0, RegionStart);

CloseHandle(hJobData);

return JobRecord.ProcessId;

}

Объекты задач

Процессы можно объединять в объекты задач (job objects), что позволяет управлять процессами как группой, устанавливать лимиты ресурсов для всех процессов, входящих в объект задачи, и вести учетную информацию. Объекты задач были впервые введены в Windows 2000 и теперь поддерживаются во всех системах NT5.

Первым шагом является создание пустого объекта задачи с помощью функции CreateObject, которая принимает два аргумента, имя и атрибуты защиты, и возвращает дескриптор объекта задачи. Существует также функция OpenJobObject, которую можно применять к именованным объектам задач. Для уничтожения объектов используется функция CloseHandle.

Функция AssignProcessToJobObject просто добавляет процесс с указанным дескриптором в объект задачи; она принимает только два параметра. Процесс может принадлежать только одной задаче, поэтому в тех случаях, когда процесс, связанный с указанным дескриптором, уже является элементом какого-либо задания, функция AssignProcessToJobObject завершается с ошибкой. Добавляемый в задачу процесс наследует значения всех ограничений, связанных с задачей, и добавляет в задачу свою учетную информацию, например использованное процессорное время.

По умолчанию новый дочерний процесс, созданный функцией CreateProcess, также принадлежит задаче, если только в аргументе dwCreationFlags при вызове функции CreateProcess не был задан флаг CREATE_BREAKWAY_FROM_JOB. В предусмотренном по умолчанию случае попытки назначения дочернего процесса задаче при помощи функции AssignProcessToJobObject приводят к ее сбойному завершению.

Наконец, для установления управляющих лимитов процессов, входящих в задачу, используется функция SetInformationJobObject.

BOOL SetInformationJobObject(HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, LPVOID lpJobObjectInformation, DWORD cbJobObjectInformationLength)

• hJob — дескриптор существующего объекта задачи.

• JobObjectInformationClass — указывает информационный класс устанавливаемых ограничений. Всего существует пять возможных значений; одним из них является значение JobObjectBasicLimitInformation, используемое для указания такой информации, как ограничения общего времени и времени, приходящегося на один процесс, ограничения размеров рабочего набора (working set)[17], ограничения на количество активных процессов, приоритет и родство процессоров (в SMP-системах родственными называются процессоры, которые могут использоваться потоками в процессах задач).

• lpJobObjectInformation — указывает на фактическую информацию, необходимую для предыдущего параметра. Для каждого информационного класса существует своя структура.

• JOBOBJECT_BASIC_ACCOUNTING_INFORMATION — позволяет получить суммарные временные характеристики (пользовательское, системное и истекшее время) процессов, входящих в задачу.

• Значением последнего параметра является размер предыдущей структуры.

Функция QueryJobInformationObject позволяет получить значения текущих ограничений. Другие информационные классы устанавливают ограничения в отношении пользовательского интерфейса, портов завершения ввода/вывода (см. главу 14), атрибутов защиты, а также завершения задачи.

Резюме

Windows предоставляет простой механизм управления процессами и синхронизацией их выполнения. Приведенные примеры продемонстрировали способы управления параллельным выполнением нескольких процессов, а также получения информации о временных характеристиках каждого процесса. Отношения предок-потомок между процессами в Windows не поддерживаются, так что в необходимых случаях управление этой информацией возлагается на программиста.

В следующих главах

В следующей главе описываются потоки, являющиеся независимыми единицами выполнения внутри процесса. В некоторых отношениях управление потоками аналогично управлению процессами; все, что связано с кодами завершения, прекращением выполнения и ожиданием завершения, применимо и к потокам. Чтобы продемонстрировать эту аналогию, самый первый из рассматриваемых в главе 7 примеров является переделанным вариантом программы grepMP (программа 6.1), который приспособлен для работы с потоками.

Глава 8 ознакомит вас с методами синхронизации, которые могут быть использованы для координации выполнения потоков, принадлежащих одному и тому же или различным процессам.

Упражнения

6.1. Расширьте возможности программы 6.1 (grepMP) таким образом, чтобы она принимала также параметры командной строки, а не только текстовый шаблон.

6.2. Вместо того чтобы передавать дочернему процессу имя временного файла, как это делается в программе 6.1, преобразуйте наследуемый дескриптор файла к типу DWORD (для типа HANDLE требуется 4 байта), а затем в строку символов. Передайте эту строку дочернему процессу в командной строке. В свою очередь, дочерний процесс должен осуществить обратное преобразование строки символов в значение дескриптора файла, который будет использован для вывода. Эту методику иллюстрируют программы catHA.с и grepHA.с, доступные на Web-сайте книги.

6.3. Программа 6.1 ожидает завершения всех процессов и лишь после этого выводит результаты. При этом возможность определения того, в каком именно порядке завершились процессы внутри программы, отсутствует. Модифицируйте программу таким образом, чтобы она определяла очередность завершения процессов. Подсказка. Измените вызов функции WaitForMultipleObjects таким образом, чтобы возврат из нее осуществлялся после завершения каждого отдельного процесса. Другой возможностью является сортировка времени завершения процессов.

6.4. В программе 6.1 временные файлы должны удаляться явным образом. Возможно ли использование флага FILE_FLAG_DELETE_ON_CLOSE при создании временных файлов таким образом, чтобы избавиться от необходимости удаления указанных файлов?

6.5. Определите, заметны ли какие-либо преимущества программы grepMP в отношении производительности (по сравнению с простой последовательной обработкой) в случае SMP-систем, если такая возможность у вас имеется, или при размещении файлов на отдельных или сетевых дисках. Частичные результаты соответствующих тестов приведены в приложении В.

6.6. Можете ли вы предложить способ, возможно, связанный с использованием объектов задач, для определения времени, затраченного на выполнение операций в пользовательском режиме и в режиме ядра? Использование объектов задач может потребовать внесения изменений в программу grepMP.

6.7. Улучшите функцию grepMP (программа 6.5) таким образом, чтобы она сообщала код завершения для каждой завершенной задачи. Кроме того, организуйте вывод временных характеристик (истекшего времени, времени работы в режиме ядра и времени работы в пользовательском режиме) суммарно для всех процессов.

6.8. У функций управления задачами есть один трудно устранимый недостаток. Предположим, что задача уничтожена и что главная программа повторно использует идентификатор процесса данного задания еще до того, как этот идентификатор будет удален из файла управления задачами. Вместе с тем, ранее этот идентификатор мог быть использован функцией OpenProcess для создания дескриптора какого-либо процесса, хотя теперь этот же идентификатор ссылается на совершенно другой процесс. Чтобы устранить возможность появления проблем подобного рода, требуется создать вспомогательный процесс, в котором будут храниться копии дескрипторов каждого созданного процесса, что позволит избежать повторного использования идентификаторов. Другая возможная методика заключается в сохранении времени запуска процесса в файле управления задачами. Это время должно совпадать со временем запуска процесса, полученного с использованием идентификатора. Примечание. Идентификаторы процессов быстро исчерпываются, и поэтому вероятность их повторного использования очень велика. В UNIX для получения идентификаторов новых процессов применяются последовательно увеличиваемые значения 32-битового счетчика, так что идентификаторы могут повторяться только после исчерпания этих значений, что происходит очень редко. В отличие от этого, в программах Windows никогда нельзя полагаться на то, что идентификатор процесса не будет повторно использован.

6.9. Измените программу JobShell таким образом, чтобы информация сохранялась в реестре, а не во временном файле.

6.10. Измените программу JobShell таким образом, чтобы процессы связывались с объектом задачи. Наложите временные и другого рода ограничения на объекты задач, предоставив пользователю возможность ввода числовых значений некоторых из этих ограничений.

6.11. Улучшите программу JobShell таким образом, чтобы команда jobs обеспечивала подсчет числа дескрипторов, используемых каждой из задач. Подсказка. Воспользуйтесь функцией GetProcessHandleCount, для которой требуется NT 5.1.

6.12. Создайте проект Version (находится на Web-сайте), использующий программу verison.c. Попытайтесь произвести пробные запуски этой программы под управлением как можно большего числа различных версий Windows, к которым у вас имеется доступ, включая Windows 9x и NT 4.0, если это возможно. Каковы старшие и младшие номера версий для этих систем, полученные вами, и какую дополнительную информацию о версиях вам удалось получить?

ГЛАВА 7

Потоки и планирование выполнения

Основной единицей выполнения в Windows является поток, и одновременно несколько потоков могут выполняться в рамках одного процесса, разделяя его адресное пространство и другие ресурсы. В главе 6 процессы ограничивались только одним потоком, однако существует много ситуаций, в которых возможность использования более одного потока была бы весьма желательной. Настоящая глава посвящена описанию потоков и иллюстрации областей их применения. Глава 8 продолжает эту тему описанием объектов синхронизации и анализом как положительных, так и отрицательных аспектов использования потоков, в то время как в главе 9 исследуется проблема повышения производительности и компромиссные способы ее решения. В главе 10 описываются методы и модели программирования объектов синхронизации, позволяющие значительно упростить проектирование и разработку надежных многопоточных программ. Эти методы применяются далее во всей оставшейся части книги.

Завершается настоящая глава кратким обсуждением облегченных потоков, посредством которых можно создавать отдельные задачи в контексте потоков. Ввиду того, что облегченные потоки используются довольно редко, можно предположить, что многие читатели предпочтут пропустить этот материал при первом чтении.

Обзор потоков

Поток (thread) — это независимая единица выполнения в контексте процесса. Программист, разрабатывающий многопоточную программу, должен организовать выполнение потоков таким образом, чтобы это позволило упростить программу и воспользоваться предоставляемыми самим хост-компьютером возможностями распараллеливания задач.

При традиционном подходе программы выполняются в виде единственного потока. Несмотря на возможность организации параллельного выполнения нескольких процессов, что было продемонстрировано на ряде примеров в главе 6, и даже их взаимодействия между собой посредством таких механизмов, как разделение памяти или каналы (глава 11), однопоточные процессы имеют ряд недостатков.

• Переключение между выполняющимися процессами потребляет заметную долю временных и других ресурсов ОС, а в случаях, аналогичных многопроцессному поиску (grepMP, программа 6.1), все процессы заняты выполнением одной и той же программы. Организация параллельной обработки файла с помощью потоков в контексте единственного процесса позволяет снизить общие накладные расходы системы.

• Не считая случаев разделения памяти, между процессами существует лишь слабая взаимосвязь, а организация разделения ресурсов, например, открытых файлов, вызывает затруднения.

• С использованием только однопоточных процессов трудно организовать простое и эффективное управление несколькими параллельно выполняющимися задачами, взаимодействующими между собой, в таких, например, случаях, как ожидание и обработка пользовательского ввода, ожидание ввода из файла или сети и выполнение вычислений.

• Тесно связанные с выполнением операций ввода/вывода программы, подобные рассмотренной в главе 2 программе преобразования файлов из ASCII в Unicode (atou, программа 2.4), вынуждены ограничиваться простой моделью чтение-изменение-запись. При обработке последовательностей файлов гораздо эффективнее инициализировать выполнение как можно большего числа операций чтения. Windows NT предлагает дополнительные возможности перекрывающегося асинхронного ввода/вывода (глава 14), однако потоки позволяют добиться того же эффекта с меньшими усилиями.

• В SMP-системах планировщик Windows распределяет выполнение отдельных потоков между различными процессорами, что во многих случаях приводит к повышению производительности.

В этой главе обсуждаются потоки и способы управления ими. Использование потоков рассматривается на примере задач параллельного поиска и многопоточной сортировки содержимого файлов. Эти две задачи позволяют сопоставить применение потоков в операциях ввода/вывода и в операциях, связанных с выполнением интенсивных вычислений. Кроме того, в этой главе представлен общий обзор планирования выполнения процессов и потоков в Windows.

Перспективы и проблемы

Согласно принятой в этой и последующих главах точке зрения использование потоков не только позволяет упростить проектирование и реализацию некоторых программ, но и (при условии соблюдения нескольких элементарных правил и следования определенным моделям программирования) обеспечивает повышение производительности и надежности программ, а также делает более понятной их структуру и облегчает их обслуживание. Функции управления потоками весьма напоминают функции управления процессами, так что, например, наряду с функцией GetProcessExitCode существует также функция GetThreadExitCode.

Указанная точка зрения не является общепринятой. Многие авторы и разработчики программного обеспечения обращают внимание на всевозможные риски и проблемы, которые возникают в случае использования потоков, и отдают предпочтение использованию нескольких процессов, когда требуется параллелизм операций. К числу проблем упомянутого рода относятся следующие:

• Поскольку потоки разделяют общую память и другие ресурсы, принадлежащие одному процессу, существует вероятность того, что один поток может случайно изменить данные, относящиеся к другому потоку.

• При определенных обстоятельствах вместо улучшения производительности может наблюдаться ее резкое ухудшение.

• Разделение потоками общей памяти и других ресурсов в контексте одного процесса может стать причиной нарушения условий состязаний между процессами и вызывать блокирование некоторых из них.

Некоторых проблем, с которыми действительно приходится сталкиваться, можно избежать, тщательно проектируя и программируя соответствующие задачи, тогда как природа других проблем обусловлена самим параллелизмом, независимо от того, реализуется он путем разбиения процессов на потоки, использованием нескольких процессов или применением специальных методов, например, методов асинхронного ввода/вывода, предоставляемых Windows.

Алина Загитова — Произвольная программа и прыжки на тренировке ЧЕ 2019


Похожие статьи.

Понравилась статья? Поделиться с друзьями: