Синхронизация с потоков с помощью объектов ядра 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Синхронизация с потоков с помощью объектов ядра



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

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

Рассмотрим, как синхронизировать потоки с помощью объектов ядра. Такие объекты предоставляют куда больше возможностей, чем механизмы синхронизации в пользовательском режиме. В сущности, единственный их недостаток — меньшее быстродействие.

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

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

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

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

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

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

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

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

Рисунок 12.1. Свободное и занятое состояние объектов

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

Рисунок 12.2. Спящий и выполняющийся потоки

 

Wait-функции

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

DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);

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

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

WaitForSingleObject(hProcess, INFINITE);

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

Вот пример, иллюстрирующий, как вызывать 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 мс, если процесс к тому времени не закончит свою работу. Заметьте, что в параметре dwMilliseconds можно передать 0, тогда WaitForSingleObject немедленно вернет управление.

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

Функция 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 идентичен одноименному параметру функции WaitForSingleObject. Если указать конкретное время ожидания, то пo его истечении функция в любом случае возвращает управление. И опять же, в этом параметре обычно передают INFINITE (будьте внимательны при написании кода, чтобы не создать ситуацию взаимной блокировки).

Возвращаемое значение функции WaitForMultipleObjects сообщает, почему возобновилось выполнение вызвавшего ее потока. Значения WAIT_FAILED и WAIT_TIMEOUT никаких пояснений не требуют. Если Вы передали TRUE в параметре fWaitAll и все объекты перешли в свободное состояние, функция возвращает значение WAIT_OBJECT_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 сканирует массив описателей (начиная с нулевого элемента), и первый же освободившийся объект прерывает ожидание. Это может привести к нежелательным последствиям. Например, поток ждет завершения трех дочерних процессов; при этом передали функции массив с их описателями. Если завершается процесс, описатель которого находится в нулевом элементе массива, WaitForMultipleObjects возвращает управление. Теперь поток может сделать то, что ему нужно, и вновь вызвать эту функцию, ожидая завершения другого процесса. Если поток передаст те же три описателя, функция немедленно вернет управление, и снова получите значение WAIT_OB JECT_0. Таким образом, пока не удалить описатели тех объектов, об освобождении которых функция уже сообщила, код будет работать некорректно.



Поделиться:


Последнее изменение этой страницы: 2016-04-26; просмотров: 302; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.222.163.31 (0.054 с.)