Синхронизация потоков в пользовательском режиме

Ограничение в использовании критических разделов. Организация последовательного доступа к ресурсам с использованием объектов Mutex. Применение функции CreateMutex для создания объекта Mutex. События как примитивная разновидность объектов синхронизации.

Рубрика Коммуникации, связь, цифровые приборы и радиоэлектроника
Вид реферат
Язык русский
Дата добавления 06.10.2010
Размер файла 42,3 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

СИНХРОНИЗАЦИЯ ПОТОКОВ В ПОЛЬЗОВАТЕЛЬСКОМ РЕЖИМЕ

1 ИСКЛЮЧАЮЩИЕ СЕМАФОРЫ (MUTEX)

Существует одно ограничение в использовании критических разделов. Оно заключается в том, что их можно применять для синхронизации потоков только в рамках одного процесса. Но бывают случаи, когда необходимо синхронизировать действия потоков различных процессов, которые разделяют какие-либо ресурсы (например, память). Использовать критические разделы в такой ситуации нельзя. Вместо них подключаются объекты типа mutex (mutex object).

Составное слово "mutex" происходит из словосочетания "mutual exclusion", что означает взаимное исключение, и очень точно отражает назначение объектов. Мы хотим предотвратить возможность прерывания потока в программе до тех пор, пока не будет выполнено обновление или использование разделяемых данных.

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

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

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

Если поток является владельцем Mutex, он обладает правом эксклюзивного использования ресурса, который защищается этим Mutex'ом. Ни один другой поток не может завладеть Mutex'ом, который уже принадлежит одному из потоков.

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

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

Для мьютексов определены следующие правила:

– создание объекта Mutex;

– если его идентификатор потока равен 0 (у самого потока не может быть та кой идентификатор), мьютекс не захвачен ни одним из потоков и находится в свободном состоянии;

– если его идентификатор потока не равен 0, мьютекс захвачен одним из потоков и находится в занятом состоянии;

– в отличие от других объектов ядра мьютексы могут нарушать обычные правила, действующие в операционной системе

При работе с Mutex выполняются следующие действия:

- создание объекта Mutex;

- освобождение идентификатора объекта Mutex;

- открытие существующего Mutex;

- овладевание объектом Mutex;

- освобождение объекта Mutex.

Создание объекта Mutex.

Для создания объекта Mutex применяется функция CreateMutex(), которая имеет следующий вид

HANDLE CreateMutex (LPSECURIT_ATTRIBUTES lpSecurityAttribs, BOOL bInitialOwner, LPCTSTR lpszMutexName)

Параметры:

lpSecurityAttrib - указатель на структуру атрибутов защиты. Если атрибуты не используются, он может содержать значение, равное NULL;

bInitialOwner - начальное состояние Mutex. Если он имеет значение TRUE, то поток, который его создает, будет сразу им владеть. Если же значение этого параметра равно FALSE, после создания объект Mutex не будет принадлежать ни одному потоку, пока не будет захвачен ими явным образом;

lpszMutexName- указатель на строку, содержащую имя Mutex. Имя не должно совпадать с именем существующего события, семафора или объекта отображения файла. Все эти объекты разделяют одно пространство имен. В именах объектов учитывается регистр и не могут присваиваться символы обратной косой черты. Максимальная длина мьютекса определяется длинай MAX_PATH;

Возвращаемое значение. При успешном выполнении функция возвращает хэндл созданного объекта Mutex, в противном случае - возвращается NULL.

Если объект Mutex создается только для использования потоками в рамках одного процесс, вместо адреса (третий параметр функции CreateMutex) имени можно задать значение NULL.

Возможна ситуация, когда процесс попытается создать объект Mutex с именем, которое уже используется в системе другим объектом Mutex. В этом случае функция CreateMutex() вернет идентификатор существующего объекта Mutex, а функция существующего объекта Mutex, а функция GetLastError(), вызванная сразу после вызова функции CreateMutex(), вернет значение ERROR_ALREADY_EXISTS.

Освобождение идентификатора объекта Mutex

Если объект Mutex больше не нужен, производится освобождение его идентификатора при помощи универсальной функции CloseHandle(). Заметим тем не менее, что при завершении процесса освобождаются идентификаторы всех объектов Mutex, созданных для него.

Открытие объекта Mutex.

Зная имя объекта Mutex, поток может его открыть с помощью функции OpenMutex(). Эта функция дает возможность нескольким процессам возможность открывать хэндл одно и того же мьютекса. Синтаксис функции следующий

HANDLE OpenMutex (DWORD dwAccessFlag, BOOL bInherit, LPCTSTR lpszMutexName)

Параметры:

dwAccessFlag - требуемый тип доступа к мьютексу;

bInherit - определяет возможность наследования порожденными процессами, если установлен TRUE;

lpszMutexName- указатель на строку, содержащую имя Mutex;

Возвращаемое значение. При успешном выполнении функция возвращает хэндл объекта Mutex, в противном случае - возвращается NULL.

Флаги доступа, передаваемые через параметр dwAccessFlag, определяю требуемый уровень доступа к объекту Mutex. Этот параметр комбинируется из следующих значениний:

Значение

Описание

EVENT_ALL_ACCESS

Указаны все возможные флаги доступа

SYNCHRONIZE

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

Зная идентификатор объекта Mutex, полученный от функций CreateMutex() или OpenMutex(), поток может завладеть объектом при помощи функций ожидания событий, например при помощи функций WaitForSingleObject() или WaitForMultipleObjects().

Функция WaitForSingleObject() возвращает управление, как только идентификатор объекта, передаваемый ей в качестве параметра, перейдет в отмеченное состояние. Если объект Mutex не принадлежит ни одному потоку, его состояние будет отмеченным.

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

Захват объекта Mutex во владение по своему значению аналогичен входу в критический раздел (секцию).

Освобождение объекта Mutex.

Для отказа от владение объектом Mutex (т.е. для его освобождения) используется функция ReleaseMutex(). В случае успешного завершения функции объект Mutex переходит в несигнальное состояние. Синтаксис функции следующий

BOOL ReleaseMutex (HANDLE hMutex)

Параметры:

hMutex - хэндл освобождаемого объекта Mutex. Он должен принадлежать потоку;

Возвращаемое значение. При успешном выполнении функция возвращается TRUE, в противном случае - возвращается FALSE.

Таким образом, проводя аналогию с критическими разделами, освобождение объекта Mutex соответствует выходу из критического раздела.

2 СОБЫТИЯ

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

Ответом на событие обычно является возобновление одного или нескольких потоков. Фактически события можно понимать как попытку реализовать функциональность условных переменных без интеграции с мъютексами.

Схема использования событий достаточно проста (рис.1).

Один из потоков создает объект-событие, вызывая для этого функцию CreateEvent(). При этом событие имеет имя, которое доступно всем потокам активных процессов. В процессе создания или позже этот поток приводит событие к исходному состоянию (отмечено или не отмечено).

Вызывая функцию WaitForSingleObject() или WaitForMultipleObject(), поток может выполнять ожидание момента, когда событие перейдет в отмеченное состояние.

Другой поток, принадлежащий тому же самому или другому процессу, может получить идентификатор события по его имени, например с помощью функции OpenEvent(). Далее, пользуясь функциями SetEvent(), ResetEvent() или PulseEvent(), эта задача может изменить состояние объекта.

Различают два типа событий - с автоматическим (auto reset event) и ручным сбросом (manual reset event), а также два средства их сигнализации - функции PulseEvent() и SetEvent().

Какой-нибудь способ сигнализации можно использовать с разным типом событий. В сочетании это дает четыре варианта действий. Рассмотрим такие действия одну за одной, но сначала остановимся па тому, как создавать события.

Для того чтобы использовать событие, его нужно создать. Делается это посредством функции CreateEvent(), описание которой, приведенное ниже

HANDLE CreateEvent (LPSECURIT_ATTRIBUTES lpEventSecurity, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpszEventName)

Параметры:

lpThreadSecurity - указатель на структуру, содержащую атрибуты доступа к событию. Если атрибуты не используются, он может содержать значение, равное NULL;

bManualReset - определяет тип события. Если значение этого параметра равно TRUE, то создается объект, для сброса которого в свободное состояние необходимо использовать функцию ResetEvent(). При значении FALSE создается событие, автоматически сбрасывающееся в свободное состояние.

bInitialState - определяет начальное состояние создаваемого события. Значение TRUE определяет, что создается событие в СВОБОДНОМ состоянии. Если поток намерен создать событие в занятом состоянии, то он должен установить этот параметр в FALSE.

lpszEventName- указатель на строку, содержащую имя создаваемого объекта - события.

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

Когда значение bManualReset равняется TRUE, то имеем событие с ручным сбросом, а когда оно равняется FALSE -- с автоматическим.

HANDLE hEvent=CreateEvent(0,TRUE,FALSE,0);// ручной сброс

Для ожидания сигнализации события используют функцию ожидания

WaitForSingleObject(hEvent, INFINITE),

Теперь рассмотрим разные варианты изменения состояния событий. Отметим, что все функции сигнализации события и сброса ее состояния имеют одинаковый синтаксис.

Для того чтобы сигнализировать о наступлении события, в потоке должна присутствовать функция SetEvent(), переводящая событие в свободное состояние, описанная в winbase.h следующим образом:

BOOL SetEvent (HANDLE hEvent)

Параметры:

hEvent - хэндл события, созданного посредством CreateEvent().

Возвращаемое значение. При успешном выполнении функция возвращает TRUE, в противном случае - возвращается FALSE.

Каким образом прикладная программа может узнать о наступлении события? Да с помощью уже знакомой нам функции WaitForSingleObject().

Для того чтобы сбросить событие в занятое состояние, необходимо вызвать функцию ResetEvent(), синтаксис которой следующий:

BOOL ResetEvent (HANDLE hEvent)

Параметры:

hEvent - хэндл события, созданного посредством CreateEvent().

Возвращаемое значение. При успешном выполнении функция возвращает TRUE, в противном случае - возвращается FALSE.

Достаточно часто встречаются случаи, когда после установки события с помощью SetEvent() тут же следует вызов ResetEvent(). Для объединения этих двух действий в одно и предусмотрена функция PulseEvent(), описанная так:

BOOL PulseEvent (HANDLE hEvent)

Параметры:

hEvent - хэндл события, созданного посредством CreateEvent().

Возвращаемое значение. При успешном выполнении функция возвращает TRUE, в противном случае - возвращается FALSE.

Во время выполнения функции SetEvent() происходит сигнализация события. Последующие действия зависят от типа события.

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

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

Выполнение функции PulseEvent() подобное до двух последовательных вызовов SetEvent() и ResetEvent(). При этом событие сигнализирует и возобновляет один из потоков, которые ожидают на события (для событий а автоматическим сбросом), или все потоки, что ожидают (для событий с ручным сбросом), затем состояние события сбрасывает. Когда ни одного потока, что ожидает нет, событие немедленно сбрасывает и факт ее сигнализации исчезает.

Вызов функции SetEvent() хранит состояние события, а вызов функции PulseEvent() - нет.


Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.