ЛАБОРАТОРНАЯ РАБОТА № 3
СРЕДСТВА СИНХРОНИЗАЦИИ ПОТОКОВ И ТУПИКИ
Цель работы - практическое знакомство с методами синхронизации двух потоков одного процесса с помощью критических секций, мьютексов, семафоров и событий, а также средствами анализа тупиковых ситуаций.
КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
В случае одновременной модификации глобальной переменной двумя и более потоками возможна потеря выполненных изменений. Для правильной работы приложений с несколькими потоками необходимо обеспечить поочередный доступ потоков к операторам программы, выполняющим изменение и запись значений переменной в память (критическим участкам).
Только один процесс должен иметь возможность изменять и записывать значения глобальных переменных.
Участок программного кода, в котором поток имеет возможность обращаться к общему неразделяемому ресурсу с целью его модификации, называется критическим участком.
Правильная работы программы, содержащей критические участки, возможна только при поочередном выполнении потоками программы критических участков.
|
|
Для решения этой задачи могут использоваться простые средства – критические секции, обеспечивающие поочередный доступ к критическим участкам потоков одного процесса, и более сложные средства – Mutex (mutually exclusive – взаимно - исключающий) - мьютексы, решающие такую же задачу для потоков, созданных различными процессами. Кроме того, задача обеспечения поочередного доступа потоков к критическим участкам может быть решена с помощью универсальных средств синхронизации - семафоров и событий.
В рассмотренном ниже приложении два потока увеличивают значение глобальной переменной Global от начального значения 100 на 1 при каждом выполнении цикла. Число повторений цикла каждого потока равно 12, результаты увеличения переменной Global выводятся на экран с помощью двух компонентов ListBox.
При правильной работе приложения конечным значением переменной должно быть число 100+12+12=124. Однако без специальных мер такого значения получить в общем случае не удается, так как потоки " мешают ” друг другу.
1.1 Критические секции
Критические секции – средство синхронизации потоков одного приложения. Критические секции обеспечивают поочередный доступ потоков к критическим участкам программы. Для использования критической секции ее необходимо создать, то есть объявить глобальную переменную sect1 типа TRTLCriticalSection и инициализировать ее с помощью вызова функции
initializeCriticalSection (sect1)
Для обеспечения поочередного доступа потоков к критическому участку в начале критического участка вызывается функция
|
|
EnterCriticalSection (имя секции), а в конце – функция
LeaveCriticalSection (имя секции).
После окончания использования критической секции она уничтожается вызовом функции DeleteCriticalSection(sect1).
1.2 Мьютексы
Мьютекс – объект ядра, используемый как средство синхронизации доступа потоков одного или нескольких приложений к критическому участку.
Мьютекс создается с помощью функции
Createmutex (
lpMutexAttributes: LPSECURITY_ATTRIBUTES; // обычно равен nil указатель на атрибут
// безопасности
False; // принадлежность создавшему потоку
lpSemaphoreAttributes: LPSECURITY_ATTRIBUTES // обычно равен nil
): Thandle;
В начале критического участка вызывается функция
Waitforsingleobject (hmut, infinite); а в конце - функция
Releasemutex (hmut) – освобождение критического участка (мьютекса).
Удаление объекта ядра Mutex выполняет функция
CloseHandle (hmut).
1.3 Семафоры
Семафор – объект ядра, используемый как универсальное средство управления ресурсами. Как правило, семафор используется для учета неразделяемых ресурсов. В данной работе семафор должен использоваться для синхронизации двух конкурирующих потоков одного приложения.
Семафор создается с помощью функции
CreateSemaphore(
lpSemaphoreAttributes: LPSECURITY_ATTRIBUTES, // обычно равен nil
Count: integer; // начальное значение счетчика свободных ресурсов
MaximumCount: integer; // максимальное количество свободных ресурсов
lpName: LPCTSTR // имя семафора - обычно nil
): Thandle;
В начале критического участка вызывается функция
Waitforsingleobject (hmut, infinite); а в конце - функция
ReleaseSemaphore (hsem, 1, nil) - освобождение критического участка (семафора).
Удаление объекта ядра Semsphore выполняет функция
CloseHandle (hmut).
1.4 События
Событие с автоматическим сбросом создается с помощью функции
CreateEvent(
lpEventAttributes: LPSECURITY_ATTRIBUTES, // обычно равен nil
bManualReset: BOOL, // тип события - manual-reset event - False
bInitialState: BOOL, // начальное состояние initial state
lpName: LPCTSTR // имя события - обычно равен nil
): THANDLE;
В начале критического участка вызывается функция
Waitforsingleobject (hmut, infinite); а в конце - функция
SetEvent (hev) - освобождение критического участка (события).
Удаление объекта ядра Event выполняет функция
CloseHandle (hev).
1.5 Тупики и распознавание тупиков
Тупиком (deadlock) называется такое состояние вычислительной системы, в котором ДВА или более потоков находятся в заблокированном состоянии, при этом каждый из потоков ждет освобождения ресурса, занятого другим потоком.
Попавшие в тупик процессы не получают процессорного времени, однако не выполняют никакой полезной работы, бесполезно занимая ресурсы вычислительной системы. Тупики должны распознаваться и устраняться как на этапе отладки приложений, так и на этапе их эксплуатации.
Для распознавания тупика и определения потоков и попавших в тупик ресурсов могут использоваться как классические средства, например модель Холта, так и современные средства, входящие в состав ОС Vista и Windows 2008 Server.
Для обнаружения взаимных блокировок в ОС Vista и Windows 2008 Server включены следующие функции Wait Chain Traversal API:
OpenThreadWaitChainSession() – создание новой WCT – сессии
GetThreadWaitChain () – для получения массива цепочек ожидания указанного потока. Функция распознает тупики – см. [1] и [4]. Цепочка ожидания – wait chain – определяется Microsoft как последовательность потоков и синхронизирующих объектов, в которой за потоком идет объект, ожидаемый этим потоком и принадлежащий следующему потоку в этой последовательности.