Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ответы Word.docx
Скачиваний:
33
Добавлен:
16.03.2016
Размер:
352.09 Кб
Скачать

Мьютекс (mutex )

По сравнению с событием это более специализированный объект. Обычно он используется для решения такой распространенной задачи синхронизации, как доступ к общему для нескольких потоков ресурсу. Во многом он похож на событие с автосбросом. Основное отличие состоит в том, что он имеет специальную привязку к конкретному потоку. Если мьютекс находится в сигнальном состоянии – это означает, что он свободен и не принадлежит ни одному потоку. Как только некий поток дождался этого мьютекса, последний сбрасывается в нейтральное состояние (здесь он как раз похож на событие с автосбросом), а поток становится его хозяином до тех пор, пока явно не освободит мьютекс функцией ReleaseMutex, или же не завершится. Таким образом, чтобы быть уверенным, что с разделяемыми данными одновременно работает только один поток, следует все места, где происходит такая работа, окружить парой:WaitFor-ReleaseMutex:

HANDLE g_hMutex;

// Пусть описатель мьютекса хранится в глобальной переменной. Его конечно надо создать заранее, до запуска рабочих потоков. Будем считать, что это уже было сделано.

{

int iWait = ::WaitForSingleObject( g_hMutex, INFINITE );

switch( iWait ) {

case WAIT_OBJECT_0:

break;

case WAIT_ABANDONED: /* Какой-то поток завершился, забыв вызвать ReleaseMutex. Скорее всего, это означает ошибку в программе! Поэтому вставим здесь ASSERT */

ASSERT( false );

break;

default:

// Здесь должна быть обработка ошибки.

}

// Защищенный мьютексом участок кода.

ProcessCommonData();

VERIFY( ::ReleaseMutex( g_hMutex ) );

}

Чем же мьютекс лучше события с автосбросом? В приведенном примере его также можно было бы использовать, только ReleaseMutex надо было бы заменить наSetEvent. Однако может возникнуть следующая сложность. Чаще всего работать с общими данными приходится в нескольких местах. Что будет, еслиProcessCommonDataв нашем примере вызовет функцию, которая работает с этими же данными и в которой уже есть своя параWaitFor-ReleaseMutex(на практике это встречается весьма часто)? Если бы мы использовали событие, программа, очевидно, зависла бы, поскольку внутри защищенного блока событие находится в нейтральном состоянии. Мьютекс же устроен более хитро. Для потока-хозяина он всегда остается в сигнализирующем состоянии, несмотря на то, что для всех остальных потоков он при этом находится в нейтральном. Поэтому если поток захватил мьютекс, повторный вызовWaitForфункции не приведет к блокировке. Более того, в мьютекс встроен еще и счетчик, так чтоReleaseMutexдолжна быть вызвана столько же раз, сколько было вызововWaitFor. Таким образом, мы можем смело защищать каждый участок кода, работающий с общими данными, паройWaitFor-ReleaseMutex, не волнуясь о том, что этот код может быть вызван рекурсивно. Это делает мьютекс очень простым в использовании инструментом.

Семафор (semaphore)

Еще более специфический объект синхронизации. Семафор предназначен для того, чтобы ограничить максимальное число потоков, одновременно работающих с неким ресурсом. По сути, семафор – это событие со счетчиком. Пока этот счетчик больше нуля, семафор находится в сигнализирующем состоянии. Однако каждый вызов WaitForуменьшает этот счетчик на единицу до тех пор, пока он не станет равным нулю и семафор перейдет в нейтральное состояние. Подобно мьютексу, для семафора есть функцияReleaseSemaphor, увеличивающая счётчик. Однако в отличие от мьютекса семафор не привязан к потоку и повторный вызовWaitFor/ReleaseSemaphorеще раз уменьшит/увеличит счетчик.

классические семафоры-счетчики, используют следующие примитивы.

  • sema p ( ) Уменьшает значение семафора (с возможным блокированием потока)

  • sema v ( ) Увеличивает значение семафора (с возможным деблокированием ожидающего потока)

  • sema tryp ( ) Уменьшает значение семафора (если не требуется блокирование). Примитив sema__tryp () обеспечивает программисту возможность использовать на уровне пользовательских потоков технологию пережидания занятости.

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

Поток создает семафор с помощью функции CreateSemaphore():

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,LONG lMaximumCount, LPCTSTR lpName);

Первый параметр функции является указателем на структуру SECURITY_ATTRIBUTES. Эта структура содержит информацию о защите создаваемого объекта. Обычно в качестве этого параметра передается NULL и в этом случае создается объект с защитой по умолчанию. Параметр lInitiаlСоunt содержит начальное значение счетчика семафора. Параметр lMaximumCount указывает максимально возможное значение счетчика. Параметр lpName является указателем на строку, в которой содержится имя данного семафора. После того как семафор успешно создан, поток может обратиться к ресурсу, защищенному семафором, с помощью одной из wait-функций. При этом wait-функции передается дескриптор семафора. После завершения работы с ресурсом поток увеличивает значение счетчика с помощью функции ReleaseSemaphore():

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

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

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

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