Для программ, использующих несколько потоков или процессов, необходимо, чтобы все они выполняли возложенные на них функции в нужной последовательности. В среде Windows 9x для этой цели предлагается использовать несколько механизмов, обеспечивающих слаженную работу потоков. Эти механизмы называют
механизмами синхронизации.
Предположим, разрабатывается программа, в которой параллельно работают два потока. Каждый поток обращается к одной разделяемой глобальной переменной. Один поток при каждом обращении к этой переменной выполняет её инкремент, а второй – декремент. При одновременной асинхронной работе потоков неизбежно возникает такая ситуация:
- первый поток прочитал значение глобальной переменной в локальную;
- ОС прерывает его, так как закончился выделенный ему квант времени процессора, и передаёт управление второму потоку;
- второй поток также считал значение глобальной переменной в локальную, декрементировал её и записал новое значение обратно;
- ОС вновь передаёт управление первому потоку, тот, ничего не зная о действиях второго потока, инкрементирует свою локальную переменную и записывает её значение в глобальную.
Очевидно, что изменения, внесённые вторым потоком, будут утеряны.
Для исключения подобных ситуаций необходимо разделить во времени использование совместных данных. В таких случаях используются механизмы синхронизации, которые обеспечивают корректную работу нескольких потоков.
Средства синхронизации в ОС Windows:
1) критическая секция (
Critical Section) – это объект, который принадлежи процессу, а не ядру. А значит, не может синхронизировать потоки из разных процессов.
Существует так же функции инициализации (создания) и удаления, вхождения и выхода из критической секции:
создание – InitializeCriticalSection(…), удаление – DeleteCriticalSection(…),
вход – EnterCriticalSection(…), выход – LeaveCriticalSection(…).
Ограничения: поскольку это не объект ядра, то он не виден другим процессам, то есть можно защищать только потоки своего процесса.
Критический раздел анализирует значение специальной переменной процесса, которая используется как флаг, предотвращающий исполнение некоторого участка кода несколькими потоками одновременно.
Среди синхронизирующих объектов критические разделы наиболее просты.
2) mutex – mutable exclude. Это объект ядра, у него есть имя, а значит с их помощью можно синхронизировать доступ к общим данным со стороны нескольких процессов, точнее, со стороны потоков разных процессов. Ни один другой поток не может завладеть мьютексом, который уже принадлежит одному из потоков. Если мьютекс защищает какие-то совместно используемые данные, он сможет выполнить свою функцию только в случае, если перед обращением к этим данным каждый из потоков будет проверять состояние этого мьютекса. Windows расценивает мьютекс как объект общего доступа, который можно перевести в сигнальное состояние или сбросить. Сигнальное состояние мьютекса говорит о том, что он занят. Потоки должны самостоятельно анализировать текущее состояние мьютексов. Если требуется, чтобы к мьютексу могли обратиться потоки других процессов, ему надо присвоить имя.
Функции:
CreateMutex(имя) – создание, hnd=OpenMutex(имя) – открытие,
WaitForSingleObject(hnd) – ожидание и занятие,
ReleaseMutex(hnd) – освобождение, CloseHandle(hnd) – закрытие.
Его можно использовать в защите от повторного запуска программ.
3) семафор – semaphore. Объект ядра “семафор” используются для учёта ресурсов и служат для ограничения одновременного доступа к ресурсу нескольких потоков. Используя семафор, можно организовать работу программы таким образом, что к ресурсу одновременно смогут получить доступ несколько потоков, однако количество этих потоков будет ограничено. Создавая семафор, указывается максимальное количество потоков, которые одновременно смогут работать с ресурсом. Каждый раз, когда программа обращается к семафору, значение счетчика ресурсов семафора уменьшается на единицу. Когда значение счетчика ресурсов становится равным нулю, семафор недоступен.
создание CreateSemaphore, открытие OpenSemaphore,
занять WaitForSingleObject, освобождение ReleaseSemaphore
4)
событие – event. События обычно просто оповещают об окончании какой-либо операции, они также являются объектами ядра. Можно не просто явным образом освободить, но так же есть операция установки события. События могут быть мануальными (manual) и единичными (single).
Единичное событие (single event) – это скорее общий флаг. Событие находится в сигнальном состоянии, если его установил какой-нибудь поток. Если для работы программы требуется, чтобы в случае возникновения события на него реагировал только один из потоков, в то время как все остальные потоки продолжали ждать, то используют единичное событие.
Мануальное событие (manual event) — это не просто общий флаг для нескольких потоков. Оно выполняет несколько более сложные функции. Любой поток может установить это событие или сбросить (очистить) его. Если событие установлено, оно останется в этом состоянии сколь угодно долгое время, вне зависимости от того, сколько потоков ожидают установки этого события. Когда все потоки, ожидающие этого события, получат сообщение о том, что событие произошло, оно автоматически сбросится.
Функции: SetEvent, ClearEvent, WaitForEvent.
Типы событий:
1) событие с автоматическим сбросом: WaitForSingleEvent.
2) событие с ручным сбросом (manual), тогда событие необходимо сбрасывать: ReleaseEvent.
Некоторые теоретики выделяют ещё один объект синхронизации:
WaitAbleTimer – объект ядра ОС, который самостоятельно переходит в свободное состояние через заданный интервал времени (будильник).