Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
385
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

Этот код сохраняет значение элемента g_t, во временной переменной sTemp Не трудно догадаться, что на исполнение этой строки уходит всего несколько тактов процессора Далее программа сразу вызывает LeaveCriticalSection — защищать глобаль ную структуру больше не нужно Так что вторая версия программы намного лучше первой, посколькудругие потоки «отлучаются» от структуры g_s лишь на несколько таков процессора, а не на неопределенно долгое время Такой подход предполагает, что «моментальный снимок» структуры вполне пригоден для чтения оконной проце дурой, а также что оконная процедура не будет изменять элементы этой структуры

ГЛАВА 9 Синхронизация потоков с использованием объектов ядра

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

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

В этой главе мы рассмотрим, как синхронизировать потоки с помощью объектов ядра. Вы увидите, что такие объекты предоставляют куда больше возможностей, чем механизмы синхронизации в пользовательском режиме. В сущности, единственный их недостаток — меньшее быстродействие Дело в том, что при вызове любой из фун кций, упоминаемых в этой главе, поток должен перейти из пользовательского режи ма в режим ядра. А такой переход обходится очень дорого — в 1000 процессорных тактов на платформе x86 Прибавьте сюда еще и время, которое необходимо на вы полнение кода этих функций в режиме ядра.

К этому моменту я уже рассказал Вам о нескольких объектах ядра, в том числе о процессах, потоках и заданиях Почти все они годятся и для решения задач синхро низации. В случае синхронизации потоков о каждом из этих объектов говорят, что он находится либо в свободном (signaled state), либо в занятом состоянии (nonsignaled state) Переход из одного состояния в другое осуществляется по правилам, определен ным Microsoft для каждого из объектов ядра Так, объекты ядра «процесс» сразу после создания всегда находятся в занятом состоянии. В момент завершения процесса опе рационная система автоматически освобождает его объект ядра "процесс", и он на всегда остается в этом состоянии

Объект ядра «процесс» пребывает в занятом состоянии, пока выполняется сопос тавленный с ним процесс, и переходит в свободное состояние, когда процесс завер шается Внутри этого объекта поддерживается булева переменная, которая при созда нии объекта

инициализируется как FALSE («занято"). По окончании работы процесса операционная система меняет значение этой переменной на TRUE, сообщая тем са мым, что объект свободен.

Если Вы пишете код, проверяющий, выполняется ли процесс в данный момент, Вам нужно лишь вызвать функцию, которая просит операционную систему проверить значение булевой переменной, принадлежащей объекту ядра «процесс". Тут нет ни чего сложного. Вы можете также сообщить системе, чтобы та перевела Ваш поток в состояние ожидания и автоматически пробудила его при изменении значения буле вой переменной с FALSE на TRUE. Тогда появляется возможность заставить поток в родительском процессе, ожидающий завершения дочернего процесса, просто заснуть до освобождения объекта ядра, идентифицирующего дочерний процесс. В дальней шем Вы увидите, что в Windows есть ряд функций, позволяющих легко решать эту задачу.

Я только что описал правила, определенные Microsoft для объекта ядра «процесс". Точно такие же правила распространяются и на объекты ядра «поток». Они тоже сра зу после создания находятся в занятом состоянии. Когда поток завершается, опера ционная система автоматически переводит объект ядра «поток" в свободное состоя ние Таким образом, используя те же приемы, Вы можете определить, выполняется ли в данный момент тот или иной поток. Как и объект ядра «процесс", объект ядра «по ток" никогда не возвращается в занятое состояние.

Следующие объекты ядра бывают в свободном или занятом состоянии:

процессы

потоки

задания

файлы консольный ввод

уведомления об изменении файлов события ожидаемые таймеры семафоры мьютексы

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

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

Когда я только начинал осваивать всю эту тематику, я предпочитал рассматривать понятия «свободен-панят» по аналогии с обыкновенным флажком Когда объект сво боден, флажок поднят, а когда он занят, флажок опущен.

Потоки спят, пока ожидаемые ими объекты заняты (флажок опущен). Как только объект освободился (флажок поднят), спящий поток замечает это, просыпается и возобновляет выполнение.

Wait-функции

Wait-функции позволяют потоку в любой момент приостановиться и ждать освобож дения какого-либо объекта ядра. Из всего семейства этих функций чаще всего исполь зуется

WaitForSingleObject:

DWORD WaitForSingleObject( HANDLE hObject, DWORD dwMilliseconds);

Когда поток вызывает эту функцию, первый параметр, hObject, идентифицирует объект ядра, поддерживающий состояния «свободен-занят» (То есть любой объект, упомянутый в списке из предыдущего раздела.) Второй параметр, dwMilliseconds, ука зывает, сколько времени (в миллисекундах) поток готов ждать освобождения объекта.

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

WaitForSingleObject(hProcess, INFINITE);

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

0xFFFFFFFF (или -1). Разумеется, передача INFINlTE нс всегда безопасна Если объект так и не перейдет в свободное состояние, вызывающий поток никогда не проснется; одно утешение, тратить драгоценное процессорное время он при этом не будет

Вот пример, иллюстрирующий, как вызывать WaitForSingleObject co значением тай маута, отличным от INFINITE

DWORD dw = WaitForSlngleObject(hProcess, 5000);

switch (dw)

{

case WAIT_OBJECT_0:

// процесс завершается break;

case WAIT_TIMEOUT:

// процесс не завершился в течение 5000 мс break;

case WAIT_FAILED:

// неправильный вызов функции (неверный описатель?) break;

}

Данный код сообщает системе, что вызывающий поток не должен получать про цессорное время, пока не завершится указанный процесс или не пройдет 5000 мс (в зависимости от того, что случится раньше). Поэтому функция вернет управление либо до истечения 5000 мс, если процесс завершится, либо примерно через 5000 мс, если процесс к тому времени не закончит свою работу Заметьте, что в параметре dwMilli seconds можно передать 0, и гогда WaitForSingleObject немедленно вернет управление

Возвращаемое значение функции WaitForSingleObject указывает, почему вызываю щий поток снова стал планируемым Если функция возвращает WAITOBTECT_0, объ ект свободен, а если WAIT_TIMEOUT — заданное время ожидания (таймаут) истекло. При передаче неверного параметра (например, недопустимого описателя) WaitForSing leObject возвращает WAIT_ EAILED. Чтобы выяснить конкретную причину ошибки, вы зовите функцию GetLastErroY.

Функция WaitForMultipleObjects аналогична WaitForSingleObject c тем исключением, что позволяет ждать освобождения сразу нескольких объектов или какого-то одного из списка объектов:

DWORD WaitForMultipleObjects( DWOHD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds);

Параметр dwCount определяет количество интересующих Вас объектов ядра Его значениедолжло быть в пределах от 1 до MAXIMUM_WAIT_OBJECTS (в заголовочных файлах Windows оно определено как 64). Параметр phObject — это указатель на мас сив описателей объектов ядра.

WaitForMultipleObjects приостанавливает поток и засгавляет его ждать освобожде ния либо всех заданных объектов ядра, либо одного из них. Параметр fWaitAll как раз и определяет, чего именно Вы хотите от функции. Если он равен TRUE, функция не даст потоку возобновить свою работу, пока нс освободятся все объекты.

Параметр dwMilliseconds идентичен одноименному параметру функции WaitFor SingleObject Если Вы указываете конкретное время ожидания, то no его истечении функция в любом случае возвращает управление. И опять же, в этом параметре обыч но передают INFINITE (будьте внимательны при написании кода, чтобы не создать ситуацию взаимной блокировки).

Возвращаемое значение функции WaitForMultipleObjects сообщает, почему возоб новилосъ выполнение вызвавшего ее потока Значения WAIT_FAILED и WAIT_TIMEOUT никаких пояснений не требуют. Если Вы передали TRUE в параметре fWaitAll и всс объекты перешли в свободное состояние, функция возвращает значение WAIT_OB JECT_0. Если fWaitAll приравнен FALSE, она возвращает управление, как только ос вобождается любой из объектов. Вы, по-видимому, захотите выяснить, кякой именно объект освободился В этом случае возвращается значение от WAIT_OBJECT_0 до WAIT_OBJECT_0 + dwCount - 1. Иначе говоря, если возвращаемое значение не равно

WAIT_TIMEOUT или WAIT_FAILED, вычтите из него значение WAlT_OBJECT_0, и Вы получите индекс в массиве описателей, на который указывает второй параметр фун кции WaitForMultipleObjects. Индекс подскажет Вам, какой объект перешел в незаня тое состояние. Поясню сказанное на примере.

HANDLE

h[3];

h[0] =

hProcess1;

h[1]

=

hProcess2;

h[2]

=

hProcess3,

DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);

switch (dw)

{

case WAIT_FAILED:

// неправильный вызов функции (неверный описатель?) break;

case WAIT_TIMEOUT:

// ни один из объектов не освободился в течение 5000 мс break;

case WAIT_OBJECTJ) + 0:

// завершился процесс, идентифицируемый h[0], т e описателем

(hProcess1)

break;

case WATT_OBJECT_0 + 1:

// завершился процесс, идентифицируемый h[1], т e описателем

(hProcess2)

break;

case WAIT_OBJECT_0 + 2:

// завершился процесс, идентифицируемый h[2], т. e описателем

(hProcess3)

break;

}

Если Вы передаете FALSE в параметре fWaitAll, функция WaitForMultipleObjects ска нирует массив описателей (начиная с нулевого элемента), и первый же освободив шийся объект прерывает ожидание Это может привести к нежелательным последстви ям. Например, Ваш поток ждет завершения трех дочерних процессов; при этом Вы передали