Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 семестр, WinAPI, MFC.pdf
Скачиваний:
370
Добавлен:
15.06.2014
Размер:
6.17 Mб
Скачать

g_dwTimes[g_nIndex - 1] = GetTickCount(),

в результате чего элементу g_dwTimes[0] присваивается текущее системное время. Тогда получалось, что у элемента g_dwTimes[l] значение меньше, чем у g_dwTimes[0]. В этот момент, даже если система решит вытеснить второй поток, она не предоставит процессорного времени первому потоку, потому что он ждет, когда станет доступной критическая секция. Рано или поздно второй поток вновь получит квант времени и исполнит оператор:

LeaveCriticalSection(&g_CriticalSection)

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

Как видно, применение критических секций позволяет организовать поочередное обращение двух потоков к каким-то общим данным. Но в некоторых случаях существует вероятность одновременного обращения к общим данным и большего числа потоков. Тогда каждый из них должен предварительно вызвать EnterCriticalSection. Если один поток уже захватил критическую секцию, то ожидающие доступа к этой секции засыпают. Когда поток освободит критическую секцию, вызвав LeaveCriticalSection, система «разбудит» только одного ожидающего, отдав ему права на эту секцию. Другие спящие потоки останутся в том же состоянии.

Мьютексы

Эти объекты весьма похожи на критические секции — за исключением того,

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

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES Ipsa, BOOL flnitialOwner LPTSTR IpszMutexName)

Одно из главных отличий объектов-мьютексов от критических секций в том,

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

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

каждого процесса вызывает CreateMutex, и все передают ей через параметр

IpszMutexName идентичные строки. Первый вызов функции приводит к созданию

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

Проверить, действительно ли CreateMutex создала новый объектмьютекс, можно через GetLastError (но обращаться к ней нужно сразу после вызова

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

Еще один способ получить описатель мьютекса— вызвать OpenMutex:

HANDLE OpenMutex(DWORD fdwAccess BOOL flnhent LPTSTR IpszName)

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

Оба описанных выше способа требуют, чтобы у объекта-мьютекса было какое-то имя. Но существуют еще два способа, в которых имя мьютекса не нужно. Один из них построен на вызове DuphcateHandle, в другом используется механизм наследования описателей BOOL ReleaseMutex(HANDLE hMutex). Эти способы рассмотрены в предыдущем разделе.

Объект-мьютекс отличается от других синхронизирующих объектов ядра тем,

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

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

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

С объектом-мьютексом сопоставляется счетчик, фиксирующий, сколько раз данный объект передавался во владение какому-либо потоку. Поэтому, если поток вызовет WaitForSingleObject для уже принадлежащего ему объекта-мьютекса, он сразу же получит доступ к защищаемым этим объектом данным (так как система — по упомянутому счетчику — тут же определит, что поток уже владеет этим объектом-

мьютексом). Кроме того, при каждом вызове WaitForSingleObject потоком — владельцем объекта-мьютекса этот счетчик увеличивается на 1. А значит, чтобы перевести мьютекс в свободное состояние, потоку придется соответствующее число раз

вызывать ReleaseMutex. Функции EnterCritical-Section и LeaveCriticalSection действуют по отношению к критическим секциям аналогичным образом.

Семафоры

Объекты ядра «семафор» используются для учета ресурсов. Они позволяют потоку запрашивать число доступных ресурсов. Если один или более ресурсов свободны, счетчик доступных ресурсов после запроса уменьшается на 1. Семафоры осуществляют такую операцию на атомном уровне. Иными словами, когда Вы

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

Допустим, у компьютера 3 последовательных порта, их могут использовать не более трех потоков одновременно, и каждый порт можно закрепить за одним потоком. Эта ситуация — отличная возможность применить семафор. Чтобы отслеживать занятость последовательных портов, Вы создаете семафор со счетчиком, равным 3 (ведь последовательных портов у Вас именно 3). При этом нужно учесть, что семафор считается свободным, если его счетчик больше 0, и занятым, если счетчик равен 0 (меньше 0 он быть не может). При каждом вызове из потока WaitForSingleObject с передачей ей описателя семафора система проверяет: больше ли 0 счетчик ресурсов у данного семафора? Если да, уменьшает счетчик на 1 и «будит» поток. Если при вызове WaitForSingleObject счетчик семафора равен 0, система оставляет поток неактивным до того, как другой поток освободит семафор (т. е. увеличит его счетчик ресурсов).

Поскольку на счетчик ресурсов семафора могут влиять несколько потоков, семафоры (в отличие от критических секций или мьютексов) не передаются во владение какому-либо потоку. И значит, один поток может ждать объект «семафор» (уменьшив его счетчик ресурсов), а другой — освободить семафор (тем самым увеличив его счетчик).

Семафор создается вызовом CreateSemaphore:

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTE Ipsa, LONG cSemlnitial

LONG cSemMax LPTSTR IpszSemName)

Эта функция создает семафор, максимальное значение счетчика которого может достигать cSemMax В нашем примере в этот параметр следовало бы поместить число 3 (три последовательных порта). Параметр cSemlmtial позволяет задать начальное состояние счетчика. При запуске системы все последовательные порты будут свободны, значит, и сюда надо занести число 3. Но если при инициализации операционной системы Вы хотите указать, что все они заняты, введите в этот параметр 0.

Последний параметр функции, IpszSemName, присваивает семафору имя в виде строки. В дальнейшем это имя позволит получить описатель семафора из других процессов с помощью CreateSemaphore или OpenSemaphore:

HANDLE OpenSemaphore(DWORD fdwAccess

BOOL flnhent

LPTSTR

IpszName)

 

 

По семантике эта функция идентична уже рассмотренной OpenMutex.

Чтобы перевести семафор в свободное состояние (увеличить его счетчик ресурсов), вызовите ReleaseSemaphore:

BOOL ReleaseSemaphore(HANDLE hSemaphore LONG cRelease LPLONG

IplPrevious)

Она похожа на ReleaseMutex, но имеет ряд отличий.

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

с ее помощью счетчик ресурсов можно увеличивать более чем на 1 единовременно.

Параметр cRelease как раз и определяет, какими порциями должен освобождаться семафор.

Например, у нас есть программа, копирующая данные из одного последовательного порта в другой. Она должна дважды запрашивать ресурсы у семафора, соответственно дважды вызывая WaitForSmgleObject. Но освободить оба ресурса программа может через один вызов ReleaseSemaphore. Вот как это делается:

// получаем два последовательных порта

WaitForSingleObject(g_hSemSerialPort

INFINITE) WaitForSingleObject(g_hSemSenalPort

INFINITE)

// чего-то делаем с ними

 

 

// освобождаем последовательные порты

чтобы и другие // приложения могли ими

попользоваться ReleaseSemaphore(g_hSemSenalPort

2 NULL)

События

События — самая примитивная разновидность синхронизирующих объектов, резко отличающаяся от семафоров и мьютексов. Cобытия просто уведомляют об окончании какой-либо операции. Объекты «событие» бывают двух типов: со сбросом

вручную (manual-reset events) и с автосбросом (auto-reset events). Первые позволяют уведомлять об окончании операции сразу несколько потоков, вторые — только один поток.

События обычно используют в том случае, когда какой-то поток

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

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

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

мьютексов и семафоров. Событие создается функцией CreateEvent

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES Ipsa, BOOL fManualReset BOOL fInitialState, LPTSTR IpszEventName),

Параметр fManualReset (Булева переменная) сообщает системе, какое событие Вы хотите создать: со сбросом вручную (TRUE) или с автосбросом (FALSE). Параметр flnitialState определяет начальное состояние события: свободное (TRUE) или занятое (FALSE). После того как система создает объект «событие», CreateEvent возвращает описатель события, специфичный для конкретного процесса.

Потоки из других процессов могут получить доступ к этому объекту:

вызовом CreateEvent с тем же параметром IpszEventName;

наследованием описателя;

применением DuplicateHandle;

вызовом OpenEvent с передачей в параметре IpszEventName имени, совпадающего с тем, что было указано в аналогичном параметре функции

CreateEvent.

Вот что представляет собой OpenEvent

HANDLE OpenEvent(DWORD fdwAccess BOOL fInherit LPTSTR IpszName),

Закрытие событий осуществляется функцией CloseHandle.

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