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

Средства синхронизации потоков в ОС Windows (критические секции, мьютексы, семафоры, события).

Для программ, использующих несколько потоков или процессов, необходимо, чтобы все они выполняли возложенные на них функции в нужной последовательности. В среде Windows 9x для этой цели предлагается использовать несколько механизмов, обеспечивающих слаженную работу потоков. Эти механизмы называют механизмами синхронизации.

Предположим, разрабатывается программа, в которой параллельно работают два потока. Каждый поток обращается к одной разделяемой глобальной переменной. Один поток при каждом обращении к этой переменной выполняет её инкремент, а второй – декремент. При одновременной асинхронной работе потоков неизбежно возникает такая ситуация:

—         первый поток прочитал значение глобальной переменной в локальную;

—         ОС прерывает его, так как закончился выделенный ему квант времени процессора, и передаёт управление второму потоку;

—         второй поток также считал значение глобальной переменной в локальную, декрементировал её и записал новое значение обратно;

—         ОС вновь передаёт управление первому потоку, тот, ничего не зная о действиях второго потока, инкрементирует свою локальную переменную и  записывает её значение в глобальную.

Очевидно, что изменения, внесённые вторым потоком, будут утеряны.

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

Средства синхронизации в ОС Windows:

1) критическая секция (Critical Section) – это объект, который принадлежи процессу, а не ядру. А значит, не может синхронизировать потоки из разных процессов.

Существует так же функции инициализации (создания) и удаления, вхождения и выхода из критической секции:

создание – InitializeCriticalSection(…),    удаление – DeleteCriticalSection(…),

вход – EnterCriticalSection(…),                выход – LeaveCriticalSection(…).

Ограничения: поскольку это не объект ядра, то он не виден другим процессам, то есть можно защищать только потоки своего процесса.

Критический раздел анализирует значение специальной перемен­ной процесса, которая используется как флаг, предотвращающий исполнение некоторого участка кода несколькими потоками одновременно.

Среди синхронизирующих объектов критические разделы наиболее просты.

2) mutexmutable 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 – объект ядра ОС, который самостоятельно переходит в свободное состояние через заданный интервал времени (будильник).

С реализацией
многопоточности тесно связано понятие
синхронизация
— управление совместным доступом
различных потоков к одним и тем же
ресурсам (данные, процессор, экранные
формы, модемы, принтеры и т.д.). Этот
процесс очень сложный и в ОС Windows
существуют различные способы синхронизации,
которые будут рассмотрены ниже. Все
потоки в системе обычно имеют доступ к
её ресурсам – файлам, блокам памяти,
последовательным портам и т.д. Любой
поток, запрашивая нужный ресурс, должен
получить монопольный доступ к ресурсу,
что и является одной из задач синхронизации
потоков. Таким образом, основными
задачами синхронизации потоков являются:

  • монопольный
    доступ к совместно разделяемому ресурсу,
    иначе потоки могут разрушить его;

  • уведомление
    некоторых потоков о завершении каких-либо
    операций в других потоках.

Если
не использовать никаих методов
синхронизации, то можно столкнуться с
ситуаций гонок (race
condition)
и тупиков. Обе эти ситуации приведут к
остановке работы многопоточного
приложения.

Ситуация
гонок
возникает,
когда два или более потока пытаются
получить доступ к общему ресурсу и
изменить его состояние. Предположим
Поток 1 получил доступ к ресурсу и изменил
его согласно сценария своей работы.
После этого был запущен Поток 2 и он
также модифицировал этот же ресурс до
завершения Потока 1. Поток 1 полагает,
что ресурс остался без изменений, однако
он уже изменен. В зависимости от того,
когда именно был изменен ресурс и какой
это был ресурс, результаты могут быть
различными: либо вообще ничего не
произойдет, либо приложение перестанет
работать, либо какие–то данные будут
потеряны.

Тупики
имеют место
тогда, когда поток ожидает ресурс,
который в данный момент принадлежит
другому потоку. Предположим Поток 1
получает доступ к объекту А и, для того
чтобы продолжать работу, ждет возможности
получения доступа к объекту Б. В то же
время Поток 2 получает доступ к объекту
Б и ждет возможности получить доступ к
объекту А. Данная ситуация приводит к
блокированию обоих потоков. Теперь ни
Поток 1, ни Поток 2 не будут исполняться.
Возникновения ситуаций гонок и тупиков
можно избежать, если использовать методы
синхронизации, рассматриваемые ниже.

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

Атомарный
доступ (atomic access)

— монопольный захват ресурсов обращающимся
к нему потоком, — это есть основа основ
синхронизации потоков. Interlocked функции
– самый простой способ синхронизации
потоков за счёт атомарного доступа к
переменной, эти функции гарантируют
монопольный доступ к переменной типа
LONG независимо от того, какой именно код
генерируется компилятором и сколько
процессоров имеет компьютер. Важной
особенностью является то, что это самый
быстрый способ синхронизации потоков
из всех существующих. Вызов одной такой
функции требует не более 50 тактов
процессора, а при использовании
синхронизирующих объектов ядра требуется
переход в режим ядра, забирающий не
менее 1000 тактов процессора и выход из
режима ядра.
Существуют
следующие Interlocked
функции: InterlockedIncrement,
InterlockedDecrement,
InterlockedExchangeAdd,
InterlockedExchange,
InterlockedExchangePointer.
Функции
InterlockedExchange
и InterlockedExchangePointer
монопольно заменяют текущее значение
переменной типа LONG, адрес которой
передаётся в первом параметре, на
значение, передаваемое во втором
параметре.
В
32-разрядном приложении обе функции
работают с 32-разрядными значениями, но
в 64-разрядном, — первая функция работают
с 32-разрядными значениями, а вторая с
64-разрядными.
Функция
InterlockedExchangeAdd
прибавляет значение второго параметра
к первому.
Все
функции возвращают исходное значение
переменной.
Принцип
работы Interlocked-функций состоит в следующем.
Для процессоров семейства Intel
x86 эти функции выдают по шине аппаратный
сигнал, не давая другому процессору
обратиться по этому адресу. Ддя процессоров
Alpha устанавливается специальный битовый
флаг процессора, указывающий, что данный
адрес памяти сейчас занят, далее значение
считывается из памяти в регистр и
меняется в регистре, и если флаг не был
сброшен, то записывается обратно в
память.

Спин-блокировка
(spin-lock)
. Это
способ использования функции
InterlockedExchange
для монопольного доступа к разделяемому
ресурсу. Для решения этой задачи вводится
переменная, которая используется как
флаг, показывающий состояние разделяемого
ресурса (свободен/занят). Если ресурс
занят, то цикл будет работать и загружать
процессор до тех пор, пока ресурс не
освободится.
Спин-блокировка
обычно используется, когда защищаемый
ресурс занят недолго, а также для
реализации критических секций (critical
section). Данный
метод рассчитан
на одинаковый приоритет потоков,
работающих с разделяемым ресурсом, и
динамическое изменение приоритетов
потоков должно быть отключено.
При
использовании спин-блокировки нельзя
допустить попадания флага занятости
ресурса и защищаемых данных в одну
кэш-линию, это может вызвать колоссальное
снижение производительности на
многопроцессорных машинах.

Кэш-линии
процессора (CPU cache lines)

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

1. CPU1 считывает
байт и смежные с ним байты из оперативной
памяти и заполняет свою кэш-линию;

2. CPU2 повторяет
действия CPU1, после чего имеет свою копию
данных в своей кэш-линии;

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

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

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

Другой способ
синхронизации, предоставляемый
операционной системой — это критические
секции.

Критические
секции (critical section)

— это части исходного кода, которые не
могут выполняться двумя потоками в одно
и то же время. Используя критиче­скую
секцию, можно упорядочить выполнение
конкретных частей исход­ного кода.
Критические секции могут использоваться
только в пределах одно­го процесса,
одного приложения.

Система
может вытеснить поток и передать
процессорное время другому потоку, но
к защищённому ресурсу ни один поток
доступа не получит, пока данный поток
не выйдет из критической секции.
Критические секции реализованы на
основе Interlocked-функций и выполняются
очень быстро, но их недостаток заключается
в том, что они дают возможности
синхронизировать потоки только в рамках
одного процесса.
Для каждого
разделяемого ресурса используется
отдельно выделенный экземпляр структуры
CRITICAL_SECTION, который обычно является
глобальным, и с которым оперируют
специальные функции входа и выхода в/из
критической секции. Перед использованием
структуры она инициализируется функцией
InitializeCriticalSection.

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

Функция
EnterCriticalSection
просматривает
значения элементов структуры
CRITICAL_SECTION. Если ресурс свободен, то
модифицирует значения элементов
структуры, указывая, что вызывающий
поток занял ресурс.
Если значения
элементов структуры означают, что ресурс
захвачен другим потоком, то функция
переводит этот поток в режим ожидания
и система запоминает, что он хочет
получить доступ к ресурсу. Поток в
состоянии ожидания не тратит процессорного
времени.
Если на
многопроцессорной машине одновременно
стартуют две функции EnterCriticalSection,
принадлежащие одному ресурсу, то функции
всё равно корректно справятся со своей
задачей и один из потоков получит доступ
к ресурсу, другой перейдет в состояние
ожидания. Для входа в критическую секцию
также может быть использована функция
TryEnterCriticalSection,
которая отличается
от предыдущей функции тем, что не
приостанавливает выполнение потока
если ресурс занят, а возвращает FALSE.
Если ресурс
свободен, то функция модифицирует
структуру, т.е. захватит ресурс и вернёт
TRUE.

Выход
из критической секции осуществляется
функцией LeaveCriticalSection.
Функция
просматривает элементы структуры и
уменьшает счётчик захватов ресурса на
1, если его значение равно 0, то управление
будет передано ожидающему потоку в
функции EnterCriticalSection.
Если один
и тот же поток вызывал два раза
EnterCriticalSection для одного ресурса, то он
должен вызвать и два раза LeaveCriticalSection.
После
окончания использования критической
секции, её нужно удалить при помощи
функции
DeleteCriticalSection.

При
попытке потока войти в критическую
секцию, занятую другим потоком, он
немедленно приостанавливается, переходя
из пользовательского режима в режим
ядра. На многопроцессорной машине поток,
захвативший ресурс и поток, переходящий
в режим ожидания (в режим ядра), могут
выполняться на разных процессорах.
Тогда появится вероятность того, что
ресурс будет освобождён ещё до того,
как второй поток перейдёт в режим ядра.
Система позволяет задать количество
циклов спин-блокировки перед тем, как
поток перейдет в режим ожидания (режим
ядра). Для этого можно использовать
функцию InitializeCriticalSectionAndSpinCount
или SetCriticalSectionAndSpinCount.
Параметром этих функций является счетчик
блокировок, возможное значение которого
лежит в пределах от 0 до 0x00FFFFFF.
На однопроцессорной
машине значение параметра этих функций
всегда равно нулю, т.к. поток, владеющий
ресурсом, не сможет освободить его, пока
другой поток крутится в циклах
спин-блокировки.

Функция
EnterCriticalSection
переводит поток в режим ожидания на
определённое время, после чего генерируется
исключение. Длительность ожидания
определена в ключе

HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession
Manager.

По
умолчанию оно равно 2 592 000 секунд (30
суток). Если [VOID InitializeCriticalSection(…)] потерпит
неудачу, то будет выброшено исключение
STATUS_NO_MEMORY, перехватить которое можно
структурной обработкой исключений.
Функция [BOOL InitializeCriticalSectionAndSpinCount(…)] в
этом случае вернёт FALSE. Во время работы
критической секции может возникнуть
ситуация, когда два и более потоков
конкурируют за доступ к ресурсу. При
этом критическая секция создаёт объект
ядра «событие» (event kernel object). Если к такой
ситуации ещё добавится и большая нехватка
памяти, то функция EnterCriticalSection сгенерирует
исключение EXCEPTION_INVALID_HANDLE. Можно
использовать структурную обработку
исключений и
перехватывать ошибку, при этом придётся
дождаться появления свободной памяти.

Второй
способ решения этой проблемы – создать
критическую секцию функцией
InitializeCriticalSectionAndSpinCount,
передавая в параметр этой функции
значение, у которого старший бит
установлен. Таким образом память под
объект ядра «событие» будет выделена
заранее.

Рассмотрим
теперь практическое использование
критических секций.
Для нескольких
независимых разделяемых ресурсов
создаются отдельные критические секции
или точнее, экземпляры структуры
CRITICAL_SECTION.

Пример
8.1

Использование критических секций

int
iMyRes1;

char cMyRes2;

CRITICAL_SECTION
csResource1;

CRITICAL_SECTION
csResource2;

DWORD
WINAPI ThreadFunc(PVOID pvParam)

{

EnterCriticalSection(&csResource1);

//
доступ
к
ресурсу
iMyRes1

LeaveCriticalSection(&csResource1);

//
————-выполняется
код
потока—————-

EnterCriticalSection(&csResource2);

//
доступ
к
ресурсу
cMyRes2

LeaveCriticalSection(&csResource2);

//
————-выполняется
код
потока—————-

EnterCriticalSection(&csResource1);

EnterCriticalSection(&csResource2);

//
одновременный
доступ
к
нескольким
ресурсам
(iMyRes1 и
cResource2)

LeaveCriticalSection(&csResource2);

LeaveCriticalSection(&csResource1);

}

Ситуация
взаимной
блокировки
(dead lock) может
возникнуть
вследствие
несоблюдении
порядка
вхождения
в
критические
секции:

DWORD
WINAPI ThreadFunc1(PVOID pvParam)

{

EnterCriticalSection(&csResource1);
// доступ
к
ресурсу
iMyRes1

EnterCriticalSection(&csResource2);
// доступ
к
ресурсу
cMyRes2

//
одновременный доступ к нескольким
ресурсам (iMyRes1
и cMyRes2)

LeaveCriticalSection(&csResource2);

LeaveCriticalSection(&csResource1);

}

DWORD
WINAPI ThreadFunc2(PVOID pvParam)

{

EnterCriticalSection(&csResource2);
// доступ
к
ресурсу
cMyRes2

EnterCriticalSection(&csResource1);
// доступ
к
ресурсу
iMyRes1

//
одновременный доступ к нескольким
ресурсам (iMyRes1
и cMyRes2)

LeaveCriticalSection(&csResource2);

LeaveCriticalSection(&csResource1);

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • Download demo project for CriticalSection Win32 — 122 Kb
  • Download demo project for CriticalSection MFC — 9.17 Kb

Introduction

In my previous article, we discussed simple multithreaded programming in C, Win32 and MFC. Now, we see simple thread synchronization with Win32 API and MFC.

In a multithreaded environment, each thread has its own local thread stack and registers. If multiple threads access the same resource for read and write, the value may not be the correct value. For example, let’s say our application contains two threads, one thread for reading content from the file and another thread writing the content to the file. If the write thread tries to write and the read thread tries to read the same data, the data might become corrupted. In this situation, we want to lock the file access. The thread synchronization has two stages. Signaled and non-signaled.

The signaled state allows objects to access and modify data. The non-signaled state does allow accessing or modifying the data in the thread local stack.

Thread Synchronization methods:

Many of the thread synchronization methods are used to synchronize multiple threads. The following methods are used to synchronize between objects.

Thread Synchronization on different processes:

Event:

Event is a thread synchronization object used to set the signaled or non-signaled state. The signaled state may be manual or automatic depending on the event declaration.

Mutex:

Mutex is the thread synchronization object which allows to access the resource only one thread at a time. Only when a process goes to the signaled state are the other resources allowed to access.

Semaphore:

Semaphore is a thread synchronization object that allows zero to any number of threads access simultaneously.

Thread Synchronization in same process:

Critical Section

The critical section is a thread synchronization object. The other synchronization objects like semaphore, event, and mutex are used to synchronize the resource with different processes. But, the critical section allows synchronization within the same process.

The given difference is the main difference between the thread synchronization objects. The other differences between the thread synchronization are the following:

Win32 Wait Functions:

The Wait family of functions are used to wait the thread synchronization object while a process completes. The widely used functions are WaitForSingleObject and WaitForMultipleObjects functions. The WaitForSingleObject function is used for waiting on a single Thread synchronization object. This is signaled when the object is set to signal or the time out interval is finished. If the time interval is INFINITE, it waits infinitely.

The WaitForMultipleObjects is used to wait for multiple objects signaled. In the Semaphore thread synchronization object, when the counters go to zero the object is non-signaled. The Auto reset event and Mutex is non-signaled when it releases the object. The manual reset event does affect the wait functions’ state.

MFC Lock/Unlock Resource:

The MFC CMutex, CCriticalSection, CSemaphore, and CEvent classes are used to synchronize the threads in Microsoft Foundation Class library.

The CSingleLock and CMultiLock are used to control the access to the resources in multithread programming. The CSingleLock and CMultiLock classes have no base class. CSingleLock is used to lock the single synchronization object at a time. CMultiLock is used to control more than one thread synchronization objects with a particular time interval. The CSingleLock/CMultiLock Lock and Unlock member functions are used to the lock or release the resource.

The CSingleLock and CMultiLock constructors use the CSyncObject object for locking and unlocking the resource. All the Thread Synchronization classes are derived from CSyncObject base class. So, the constructor has any one of the thread synchronization classes derived from CSyncObject. CSingleLock IsLocked member is use to find if the object is locked already or not.

The CMultiLock class is used to control the access to the resources in multiple objects. The CMultiLock constructor has an array of CSyncObject objects and the total number of counts in the thread synchronization classes. The IsLocked member function is used to check the particular synchronization object state.

Thread Synchronization Objects:

Critical Section:

The Critical section object is same as the Mutex object. But, the Mutex object allows synchronizing objects across the process. But the Critical section object does not allow synchronization with different processes. The critical section is used to synchronize the threads within the process boundary.

It is possible to use Mutex instead of critical section. But, the critical section thread synchronization object is slightly faster compared to other synchronization objects. The critical section object synchronizes threads within the process. Critical section allows accessing only one thread at a time.

Win32 Critical Section Object:

The process allocates memory for the critical section using the CRITICAL_SECTION structure. The critical section structure declared in the Winnt.h is as follows:

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    
    
    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;   
    HANDLE LockSemaphore;
    DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

In critical section, we allocate memory for CRITICAL_SECTION structure and initializes the critical section. The IniailizeCriticalSection and InitializeCriticalSectionAndSpinCount are used to initialize the critical section. If we initialize the critical section, then only, we use any one of the EnterCriticalsection, TryEnterCriticalSection, or LeaveCriticalSection functions. The EnterCriticalsection function is used to enter the critical section, and TryEnterCriticalSection to enter the critical section without blocking. LeaveCricalSection is used to leave the critical section.

If any of the other synchronization object names is same as Critical section object, the Critical section object waits for the ownership infinitely. The Critical section object does not allow moving or copying the object. If we have to synchronize the thread on different processes, use Mutex object. DeleteCriticalSection function releases all the critical section objects. After calling the DeleteCriticalSection, it is not possible to call EnterCriticalsection or LeaveCriticalSection.

Example:

CRITICAL_SECTION m_cs;


InitializeCriticalSection(&m_cs);

The two threads try to access the same variable. The global variable g_n tries to access two threads. The global m_cs CRITICAL_SECTION structure is used to synchronize the two threads.

UINT ThreadOne(LPVOID lParam)
{
    
    
    EnterCriticalSection(&m_cs);

    

    
    LeaveCriticalSection(&m_cs);

return 0;
}

Thread two:

UINT ThreadTwo(LPVOID lParam)
{

    EnterCriticalSection(&m_cs); 

    

    
    LeaveCriticalSection(&m_cs);

    
    return 0;
}

MFC Critical Section object:

The CCriticalSection class provides the functionality of critical section synchronization object. The default constructor is used to construct the critical section object. The Lock and Unlock functions are used to control the resource access in the synchronization object.

The CRITICAL_SECTION‘s m_sect data member allows initializing the CRITICAL_SECTION structure. The Lock function overloaded in two forms. The Lock function without any arguments is used to lock the resource. The other form of Lock function needs the number of milliseconds to wait. All the critical section members are declared in afxmt.inl file as inline functions.

Example:

CCriticalSection c_s;
int g_C;


UINT ThreadFunction1(LPVOID lParam)
{

    
    CSingleLock lock(&c_s);

    
    lock.Lock();

    

    
    lock.Unlock();

    
    return 0;
}


UINT ThreadFunction2(LPVOID lParam)
{

    
    CSingleLock lock(&c_s);

    
    lock.Lock();

    

    
    lock.Unlock();

    
    return 0;
}

Event:

Event is the thread synchronization object to set signaled state or non-signaled state. The Event has two types. They are manual reset event and auto reset event.

The manual event has signaled user set to non-signaled state, uses ResetEvent function manually. The auto reset event automatically occurs when the object is raised to the non-signaled state. The event thread synchronization object is used to synchronize the particular event entered in the thread. The Event object is used to set the operating system kernel flag in the thread.

Win32 Event Object:

The CreateEvent function is used to create the event thread synchronization object. The manual or auto reset event choice is mentioned at the CreateEvent function parameter initialization. The Wait family functions (WaitForSingleObject, WaitForMultipleObjects) are use to wait when a particular event occurs. The group of objects waits for the events: the single object signaled or the entire events are signaled in the thread.

CreateEvent function is used to create manual or auto reset events. This function is used to create named and unnamed event objects. SetEvent function is used to set the event object to signal state. The ResetEvent function is used to set the event object to non-signaled state. If the function is successful, it returns the handle of that event. If the named event is already available, the GetLastError function returns the ERROR_ALREADY_EXISTS flag. If the named event is already available, the OpenEvent function is used to access the event previously created by the CreateEvent function.

Example:

HANDLE g_Event; 


g_Event = CreateEvent(  NULL,      TRUE,    TRUE,  "Event"     ); 

ResetEvent(g_Event);



SetEvent(g_Event);

MFC Event Object:

Event is useful for waiting if something happens (or an event occurs). CEvent class is used for event object functionality. The CEvent class is derived from the abstract CSyncObject Class. The CSyncObject is derived from the CObject mother class. The CSingleLock or CMultiLock constructor use one of the CSyncObject derived classes. The CEvent constructor specifies the manual or auto reset event options.

MFC CEvent class provides the constructor with default arguments. If we want to control our needs, we set the Ownership for the event object, manual or auto reset event flag, the name of the event object (if named event), and the security attributes. If the name matches with that of an existing event object, check the type of the object. If that object is an event, the CEvent constructor simply replies the handle of the previous event. If the name exists for any of the other synchronization object, the CEvent constructor gives an error message.

The Manual event object signaled/non-signaled uses SetEvent or ResetEvent function .The Auto Reset event occurs when it releases any one of threads in the event object. We don’t use CSyncObject directly. Because, the CSyncObject is an abstract base class. All the event members are declared in the afxmt.inl file as inline functions.

Mutex Synchronization Object:

Mutex is the synchronization object used to synchronize the threads with more than one process. The Mutex is as the name tells, mutually exclusive. The Mutex object allows accessing the resource single thread at a time.

Win32 Mutex Object:

The CreateMutex function is used to create the Mutex object. In the CreateMutex function, we initialize the named Mutex or unnamed Mutex, and set the ownership to true or false arguments.

If we create two-named Mutex using CreateMutex function within the same process, the second CreateMutex function returns error. If we create two or more Mutex objects on different processes, with the same name, when we call first time, the CreateMutex function creates the Mutex. The other CreateMutex function returns the handle of the previous Mutex object.

The OpenMutex function is used to open an existing Mutex using the supplied Mutex name. The ReleaseMutex is used to release a Mutex object. The threads wait in first in first out order for taking the ownership for the waiting threads. If we try to take the ownership twice, deadlock occurs. We the call ReleaseMutex, and then try to take the ownership.

Example

HANDLE g_Mutex;
DWORD dwWaitResult;


g_Mutex = CreateMutex( NULL, TRUE, "MutexToProtectDatabase");
 

dwWaitResult = WaitForSingleObject( g_Mutex, 5000L);




ReleaseMutex(g_Mutex))

MFC Mutex Object:

The MFC CMutex class is used to control the Mutex objects. The CMutex constructor has three parameters. We can specify the name of the Mutex as a parameter. If this is null, an unnamed Mutex is created. The Security attributes are used to set the security attributes for the Mutex object. If the name already exists for a Mutex object, the constructor simply returns the existing Mutex object. If the name exists for some object, the constructor fails.

To control resource access for single Mutex object, use CSingleLock class. If you wish to control multiple Mutex objects, the CMultiLock is used to control the access to resources in multithreaded programming.

CMutex g_m;
int g_C;

UINT ThreadFunction1(LPVOID lParam)
{

    
    CSingleLock lock(&g_m);

    lock.Lock();

    

    lock.Unlock();

    
    return 0;
}


UINT ThreadFunction2(LPVOID lParam)
{

    
    CSingleLock lock(&g_m);

    lock.Lock();

    

    lock.Unlock();

    
    return 0;
}

Semaphore Thread Synchronization Object:

Semaphore is a thread synchronization object that allows accessing the resource for a count between zero and maximum number of threads. If the Thread enters the semaphore, the count is incremented. If the thread completed the work and is removed from the thread queue, the count is decremented. When the thread count goes to zero, the synchronization object is non-signaled. Otherwise, the thread is signaled.

Win32 Semaphore Synchronization Object:

The CreateSemaphore function is used to create a named or unnamed semaphore thread synchronization object. The initial count and maximum count is mentioned in the CreateSemaphore function. The count is never negative and less then the total count value. The WaitForSingleObject waits for more than one object in semaphore object.

The WaitForMultipleObjects function is non-signaled when all the objects are returned. The OpenSemaphore function is used to open an existing handle to a semaphore object created within the process or another process. The Releasesemaphore function is used to release the semaphore from the Thread synchronization queue. If the CreateSemaphore function has created the same named Thread synchronization object within the process, the CreateSemaphore returns 0. The GetLastError function is used to retrieve the reason for the failure.

Example:

HANDLE g_Semaphore;


g_Semaphore = CreateSemaphore(  NULL,   4, 4,  NULL);  

DWORD dwWaitResult; 

dwWaitResult = WaitForSingleObject(   g_Semaphore, 0L);




ReleaseSemaphore(  g_Semaphore,  1,  NULL) ;

MFC Semaphore Synchronization Object:

The CSemaphore class allows us to create the CSemaphore object. The CSemaphore class is derived from the abstract CSyncObject base class. The CSemaphore class constructor is used to specify the count and maximum number of resource access.

The virtual destructor is used to delete the CSemaphore class object without affecting the CSyncObject base class. The Unlock function is used to unlock the resources.

Conclusion:

Thread Synchronization is used to access the shared resources in a multithread environment. The programmer decides the situation for when to use the synchronization object efficiently. The MFC Thread Synchronization classes internally call the Win32 API functions. The MFC Thread Synchronization classes wrap many of the functionalities form the Windows environment.

  • Download demo project for CriticalSection Win32 — 122 Kb
  • Download demo project for CriticalSection MFC — 9.17 Kb

Introduction

In my previous article, we discussed simple multithreaded programming in C, Win32 and MFC. Now, we see simple thread synchronization with Win32 API and MFC.

In a multithreaded environment, each thread has its own local thread stack and registers. If multiple threads access the same resource for read and write, the value may not be the correct value. For example, let’s say our application contains two threads, one thread for reading content from the file and another thread writing the content to the file. If the write thread tries to write and the read thread tries to read the same data, the data might become corrupted. In this situation, we want to lock the file access. The thread synchronization has two stages. Signaled and non-signaled.

The signaled state allows objects to access and modify data. The non-signaled state does allow accessing or modifying the data in the thread local stack.

Thread Synchronization methods:

Many of the thread synchronization methods are used to synchronize multiple threads. The following methods are used to synchronize between objects.

Thread Synchronization on different processes:

Event:

Event is a thread synchronization object used to set the signaled or non-signaled state. The signaled state may be manual or automatic depending on the event declaration.

Mutex:

Mutex is the thread synchronization object which allows to access the resource only one thread at a time. Only when a process goes to the signaled state are the other resources allowed to access.

Semaphore:

Semaphore is a thread synchronization object that allows zero to any number of threads access simultaneously.

Thread Synchronization in same process:

Critical Section

The critical section is a thread synchronization object. The other synchronization objects like semaphore, event, and mutex are used to synchronize the resource with different processes. But, the critical section allows synchronization within the same process.

The given difference is the main difference between the thread synchronization objects. The other differences between the thread synchronization are the following:

Win32 Wait Functions:

The Wait family of functions are used to wait the thread synchronization object while a process completes. The widely used functions are WaitForSingleObject and WaitForMultipleObjects functions. The WaitForSingleObject function is used for waiting on a single Thread synchronization object. This is signaled when the object is set to signal or the time out interval is finished. If the time interval is INFINITE, it waits infinitely.

The WaitForMultipleObjects is used to wait for multiple objects signaled. In the Semaphore thread synchronization object, when the counters go to zero the object is non-signaled. The Auto reset event and Mutex is non-signaled when it releases the object. The manual reset event does affect the wait functions’ state.

MFC Lock/Unlock Resource:

The MFC CMutex, CCriticalSection, CSemaphore, and CEvent classes are used to synchronize the threads in Microsoft Foundation Class library.

The CSingleLock and CMultiLock are used to control the access to the resources in multithread programming. The CSingleLock and CMultiLock classes have no base class. CSingleLock is used to lock the single synchronization object at a time. CMultiLock is used to control more than one thread synchronization objects with a particular time interval. The CSingleLock/CMultiLock Lock and Unlock member functions are used to the lock or release the resource.

The CSingleLock and CMultiLock constructors use the CSyncObject object for locking and unlocking the resource. All the Thread Synchronization classes are derived from CSyncObject base class. So, the constructor has any one of the thread synchronization classes derived from CSyncObject. CSingleLock IsLocked member is use to find if the object is locked already or not.

The CMultiLock class is used to control the access to the resources in multiple objects. The CMultiLock constructor has an array of CSyncObject objects and the total number of counts in the thread synchronization classes. The IsLocked member function is used to check the particular synchronization object state.

Thread Synchronization Objects:

Critical Section:

The Critical section object is same as the Mutex object. But, the Mutex object allows synchronizing objects across the process. But the Critical section object does not allow synchronization with different processes. The critical section is used to synchronize the threads within the process boundary.

It is possible to use Mutex instead of critical section. But, the critical section thread synchronization object is slightly faster compared to other synchronization objects. The critical section object synchronizes threads within the process. Critical section allows accessing only one thread at a time.

Win32 Critical Section Object:

The process allocates memory for the critical section using the CRITICAL_SECTION structure. The critical section structure declared in the Winnt.h is as follows:

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
    
    
    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;   
    HANDLE LockSemaphore;
    DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

In critical section, we allocate memory for CRITICAL_SECTION structure and initializes the critical section. The IniailizeCriticalSection and InitializeCriticalSectionAndSpinCount are used to initialize the critical section. If we initialize the critical section, then only, we use any one of the EnterCriticalsection, TryEnterCriticalSection, or LeaveCriticalSection functions. The EnterCriticalsection function is used to enter the critical section, and TryEnterCriticalSection to enter the critical section without blocking. LeaveCricalSection is used to leave the critical section.

If any of the other synchronization object names is same as Critical section object, the Critical section object waits for the ownership infinitely. The Critical section object does not allow moving or copying the object. If we have to synchronize the thread on different processes, use Mutex object. DeleteCriticalSection function releases all the critical section objects. After calling the DeleteCriticalSection, it is not possible to call EnterCriticalsection or LeaveCriticalSection.

Example:

CRITICAL_SECTION m_cs;


InitializeCriticalSection(&m_cs);

The two threads try to access the same variable. The global variable g_n tries to access two threads. The global m_cs CRITICAL_SECTION structure is used to synchronize the two threads.

UINT ThreadOne(LPVOID lParam)
{
    
    
    EnterCriticalSection(&m_cs);

    

    
    LeaveCriticalSection(&m_cs);

return 0;
}

Thread two:

UINT ThreadTwo(LPVOID lParam)
{

    EnterCriticalSection(&m_cs); 

    

    
    LeaveCriticalSection(&m_cs);

    
    return 0;
}

MFC Critical Section object:

The CCriticalSection class provides the functionality of critical section synchronization object. The default constructor is used to construct the critical section object. The Lock and Unlock functions are used to control the resource access in the synchronization object.

The CRITICAL_SECTION‘s m_sect data member allows initializing the CRITICAL_SECTION structure. The Lock function overloaded in two forms. The Lock function without any arguments is used to lock the resource. The other form of Lock function needs the number of milliseconds to wait. All the critical section members are declared in afxmt.inl file as inline functions.

Example:

CCriticalSection c_s;
int g_C;


UINT ThreadFunction1(LPVOID lParam)
{

    
    CSingleLock lock(&c_s);

    
    lock.Lock();

    

    
    lock.Unlock();

    
    return 0;
}


UINT ThreadFunction2(LPVOID lParam)
{

    
    CSingleLock lock(&c_s);

    
    lock.Lock();

    

    
    lock.Unlock();

    
    return 0;
}

Event:

Event is the thread synchronization object to set signaled state or non-signaled state. The Event has two types. They are manual reset event and auto reset event.

The manual event has signaled user set to non-signaled state, uses ResetEvent function manually. The auto reset event automatically occurs when the object is raised to the non-signaled state. The event thread synchronization object is used to synchronize the particular event entered in the thread. The Event object is used to set the operating system kernel flag in the thread.

Win32 Event Object:

The CreateEvent function is used to create the event thread synchronization object. The manual or auto reset event choice is mentioned at the CreateEvent function parameter initialization. The Wait family functions (WaitForSingleObject, WaitForMultipleObjects) are use to wait when a particular event occurs. The group of objects waits for the events: the single object signaled or the entire events are signaled in the thread.

CreateEvent function is used to create manual or auto reset events. This function is used to create named and unnamed event objects. SetEvent function is used to set the event object to signal state. The ResetEvent function is used to set the event object to non-signaled state. If the function is successful, it returns the handle of that event. If the named event is already available, the GetLastError function returns the ERROR_ALREADY_EXISTS flag. If the named event is already available, the OpenEvent function is used to access the event previously created by the CreateEvent function.

Example:

HANDLE g_Event; 


g_Event = CreateEvent(  NULL,      TRUE,    TRUE,  "Event"     ); 

ResetEvent(g_Event);



SetEvent(g_Event);

MFC Event Object:

Event is useful for waiting if something happens (or an event occurs). CEvent class is used for event object functionality. The CEvent class is derived from the abstract CSyncObject Class. The CSyncObject is derived from the CObject mother class. The CSingleLock or CMultiLock constructor use one of the CSyncObject derived classes. The CEvent constructor specifies the manual or auto reset event options.

MFC CEvent class provides the constructor with default arguments. If we want to control our needs, we set the Ownership for the event object, manual or auto reset event flag, the name of the event object (if named event), and the security attributes. If the name matches with that of an existing event object, check the type of the object. If that object is an event, the CEvent constructor simply replies the handle of the previous event. If the name exists for any of the other synchronization object, the CEvent constructor gives an error message.

The Manual event object signaled/non-signaled uses SetEvent or ResetEvent function .The Auto Reset event occurs when it releases any one of threads in the event object. We don’t use CSyncObject directly. Because, the CSyncObject is an abstract base class. All the event members are declared in the afxmt.inl file as inline functions.

Mutex Synchronization Object:

Mutex is the synchronization object used to synchronize the threads with more than one process. The Mutex is as the name tells, mutually exclusive. The Mutex object allows accessing the resource single thread at a time.

Win32 Mutex Object:

The CreateMutex function is used to create the Mutex object. In the CreateMutex function, we initialize the named Mutex or unnamed Mutex, and set the ownership to true or false arguments.

If we create two-named Mutex using CreateMutex function within the same process, the second CreateMutex function returns error. If we create two or more Mutex objects on different processes, with the same name, when we call first time, the CreateMutex function creates the Mutex. The other CreateMutex function returns the handle of the previous Mutex object.

The OpenMutex function is used to open an existing Mutex using the supplied Mutex name. The ReleaseMutex is used to release a Mutex object. The threads wait in first in first out order for taking the ownership for the waiting threads. If we try to take the ownership twice, deadlock occurs. We the call ReleaseMutex, and then try to take the ownership.

Example

HANDLE g_Mutex;
DWORD dwWaitResult;


g_Mutex = CreateMutex( NULL, TRUE, "MutexToProtectDatabase");
 

dwWaitResult = WaitForSingleObject( g_Mutex, 5000L);




ReleaseMutex(g_Mutex))

MFC Mutex Object:

The MFC CMutex class is used to control the Mutex objects. The CMutex constructor has three parameters. We can specify the name of the Mutex as a parameter. If this is null, an unnamed Mutex is created. The Security attributes are used to set the security attributes for the Mutex object. If the name already exists for a Mutex object, the constructor simply returns the existing Mutex object. If the name exists for some object, the constructor fails.

To control resource access for single Mutex object, use CSingleLock class. If you wish to control multiple Mutex objects, the CMultiLock is used to control the access to resources in multithreaded programming.

CMutex g_m;
int g_C;

UINT ThreadFunction1(LPVOID lParam)
{

    
    CSingleLock lock(&g_m);

    lock.Lock();

    

    lock.Unlock();

    
    return 0;
}


UINT ThreadFunction2(LPVOID lParam)
{

    
    CSingleLock lock(&g_m);

    lock.Lock();

    

    lock.Unlock();

    
    return 0;
}

Semaphore Thread Synchronization Object:

Semaphore is a thread synchronization object that allows accessing the resource for a count between zero and maximum number of threads. If the Thread enters the semaphore, the count is incremented. If the thread completed the work and is removed from the thread queue, the count is decremented. When the thread count goes to zero, the synchronization object is non-signaled. Otherwise, the thread is signaled.

Win32 Semaphore Synchronization Object:

The CreateSemaphore function is used to create a named or unnamed semaphore thread synchronization object. The initial count and maximum count is mentioned in the CreateSemaphore function. The count is never negative and less then the total count value. The WaitForSingleObject waits for more than one object in semaphore object.

The WaitForMultipleObjects function is non-signaled when all the objects are returned. The OpenSemaphore function is used to open an existing handle to a semaphore object created within the process or another process. The Releasesemaphore function is used to release the semaphore from the Thread synchronization queue. If the CreateSemaphore function has created the same named Thread synchronization object within the process, the CreateSemaphore returns 0. The GetLastError function is used to retrieve the reason for the failure.

Example:

HANDLE g_Semaphore;


g_Semaphore = CreateSemaphore(  NULL,   4, 4,  NULL);  

DWORD dwWaitResult; 

dwWaitResult = WaitForSingleObject(   g_Semaphore, 0L);




ReleaseSemaphore(  g_Semaphore,  1,  NULL) ;

MFC Semaphore Synchronization Object:

The CSemaphore class allows us to create the CSemaphore object. The CSemaphore class is derived from the abstract CSyncObject base class. The CSemaphore class constructor is used to specify the count and maximum number of resource access.

The virtual destructor is used to delete the CSemaphore class object without affecting the CSyncObject base class. The Unlock function is used to unlock the resources.

Conclusion:

Thread Synchronization is used to access the shared resources in a multithread environment. The programmer decides the situation for when to use the synchronization object efficiently. The MFC Thread Synchronization classes internally call the Win32 API functions. The MFC Thread Synchronization classes wrap many of the functionalities form the Windows environment.

Функции синхронизации

   Функции, ожидающие единственный объект

   Функции, ожидающие несколько объектов

   Прерывание ожидания по запросу на завершение операции ввода-вывода
или APC

Объекты синхронизации

   Event (событие)

   Mutex (Mutually Exclusive)

   Semaphore (семафор)

   Waitable timer (таймер ожидания)

   Дополнительные объекты синхронизации

      Сообщение об изменении
папки (change notification)

      Устройство стандартного
ввода с консоли (console input)

      Задание (Job)

      Процесс (Process)

      Поток (thread)

Дополнительные механизмы синхронизации

   Критические секции

   Защищенный доступ к переменным (Interlocked Variable Access)

Резюме

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

Главной идеей, заложенной в основе
синхронизации потоков в Win32,
является использование объектов
синхронизации и функций ожидания. Объекты
могут находиться в одном из двух состояний —
Signaled
или Not
Signaled. Функции
ожидания блокируют выполнение потока до
тех пор, пока заданный объект находится в
состоянии Not Signaled. Таким
образом, поток, которому необходим
эксклюзивный доступ к ресурсу, должен
выставить какой-либо объект синхронизации
в несигнальное состояние, а по окончании —
сбросить его в сигнальное. Остальные
потоки должны перед доступом к этому
ресурсу вызвать функцию ожидания, которая
позволит им дождаться освобождения ресурса.

Рассмотрим,
какие объекты и функции синхронизации
предоставляет нам Win32 API.

Функции синхронизации

Функции синхронизации делятся на две
основные категории: функции, ожидающие
единственный объект, и функции, ожидающие
один из нескольких объектов.

Функции, ожидающие единственный объект

Простейшей функцией ожидания является
функция WaitForSingleObject:

function WaitForSingleObject(
     hHandle: THandle;       // идентификатор    объекта
     dwMilliseconds: DWORD   // период ожидания
   ): DWORD; stdcall; 

Функция ожидает перехода объекта hHandle в сигнальное состояние в течение dwMilliseconds
миллисекунд. Если в качестве параметра dwMilliseconds передать значение INFINITE,
функция будет ждать в течение неограниченного времени. Если dwMilliseconds равен
0, то функция проверяет состояние объекта и немедленно возвращает управление.

Функция
возвращает одно из следующих значений:

WAIT_ABANDONED 
Поток, владевший объектом, завершился, не переведя объект в сигнальное
состояние
WAIT_OBJECT_0 
Объект перешел в сигнальное состояние
WAIT_TIMEOUT 
Истек срок ожидания. Обычно в этом случае генерируется ошибка либо функция
вызывается в цикле до получения другого результата
WAIT_FAILED 
Произошла ошибка (например, получено неверное значение hHandle). Более
подробную информацию можно получить, вызвав GetLastError

Следующий фрагмент кода запрещает доступ
к Action1
до перехода объекта ObjectHandle
в сигнальное состояние (например, таким
образом можно дожидаться завершения
процесса, передав в качестве ObjectHandle
его идентификатор, полученный функцией CreateProcess):

var
     Reason: DWORD;
     ErrorCode: DWORD;
   
   Action1.Enabled := FALSE;
   try
     repeat
       Application.ProcessMessages;
       Reason := WailForSingleObject(ObjectHandle, 10);
       if Reason = WAIT_FAILED then begin
         ErrorCode := GetLastError;
         raise Exception.CreateFmt(
           ‘Wait for object failed with error:    %d’, [ErrorCode]);
       end;
     until Reason <> WAIT_TIMEOUT;
   finally
     Actionl.Enabled := TRUE;
   end; 

В случае когда одновременно с ожиданием объекта требуется перевести в сигнальное
состояние другой объект, может использоваться функция SignalObjectAndWait:

function SignalObjectAndWait(
     hObjectToSignal: THandle;  // объект, который будет переведен в
                                   // сигнальное состояние
     hObjectToWaitOn: THandle;  // объект, который     ожидает    функция
     dwMilliseconds: DWORD;     // период ожидания
     bAlertable: BOOL              // задает, должна ли функция возвращать
                                //    управление в случае запроса на
                                   // завершение операции ввода-вывода
   ): DWORD; stdcall; 

Возвращаемые значения аналогичны функции WaitForSingleObject.

! В модуле Windows.pas эта функция ошибочно объявлена как возвращающая
значение BOOL. Если вы намерены ее использовать – объявите ее корректно
или используйте приведение типа возвращаемого значения к DWORD.

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

Функции, ожидающие несколько объектов

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

type
     TWOHandleArray = array[0..MAXIMUM_WAIT_OBJECTS - 1] of THandle;
     PWOHandleArray = ^TWOHandleArray;
   
   function WaitForMultipleObjects(
     nCount: DWORD;                 // Задает количество объектов
     lpHandles: PWOHandleArray;  // Адрес массива объектов
     bWaitAll: BOOL;                // Задает, требуется ли ожидание всех
                                    // объектов или любого
     dwMilliseconds: DWORD       // Период ожидания
   ): DWORD; stdcall; 

Функция возвращает одно из следующих значений:

Число в диапазоне от

       WAIT_OBJECT_0 до
       WAIT_OBJECT_0 + nCount – 1              
Если bWaitAll равно TRUE, то это число означает, что все объекты перешли
в сигнальное состояние. Если FALSE — то, вычтя из возвращенного значения
WAIT_OBJECT_0, мы получим индекс объекта в массиве lpHandles

Число в диапазоне от

       WAIT_ABANDONED_0 до
       WAIT_ABANDONED_0 + nCount – 1              
Если bWaitAll равно TRUE,  это означает, что все объекты перешли
в сигнальное состояние, но хотя бы один из владевших ими потоков завершился,
не сделав объект сигнальным Если FALSE — то, вычтя из возвращенного значения
WAIT_ABANDONED_0,  мы получим в массиве lpHandles индекс объекта,
при этом поток, владевший этим объектом,
завершился,  не сделав его сигнальным
WAIT_TIMEOUT 
Истек период ожидания
WAIT_FAILED 
Произошла ошибка

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

var
     Handles: array[0..1] of THandle;
     Reason: DWORD;
     RestIndex: Integer;
   
   ...
   
   Handles[0] := OpenMutex(SYNCHRONIZE, FALSE, ‘FirstResource’);
   Handles[1] := OpenMutex(SYNCHRONIZE, FALSE, ‘SecondResource’);
   // Ждем первый из объектов
   Reason := WaitForMultipleObjects(2, @Handles, FALSE, INFINITE);
   case Reason of
     WAIT_FAILED: RaiseLastWin32Error;
     WAIT_OBJECT_0, WAIT_ABANDONED_0:
       begin
         ModifyFirstResource;
         RestIndex := 1;
       end;
     WAIT_OBJECT_0 + 1, WAIT_ABANDONED_0 + 1:
       begin
         ModifySecondResource;
         RestIndex := 0;
       end;
     // WAIT_TIMEOUT возникнуть не может
   end;
   // Теперь ожидаем освобождения следующего объекта
   if WailForSingleObject(Handles[RestIndex],
        INFINITE) = WAIT_FAILED then
          RaiseLastWin32Error;
   // Дождались, модифицируем оставшийся ресурс
   if RestIndex = 0 then
     ModifyFirstResource
   else
    ModifySecondResource; 

Описанную выше технику можно применять,
если вы точно знаете, что задержка ожидания
объекта будет незначительной. В противном
случае ваша программа окажется «замороженной»
и не сможет даже перерисовать свое окно. Если
период задержки может оказаться
значительным, то необходимо дать программе
возможность реагировать на сообщения Windows. Выходом
может стать использование функций с
ограниченным периодом ожидания (и
повторный вызов — в случае возврата WAIT_TIMEOUT)
либо функции MsgWaitForMultipleObjects:

function MsgWaitForMultipleObjects(
   nCount: DWORD;     // количество объектов синхронизации
   var pHandles;      // адрес массива объектов
   fWaitAll: BOOL;    // Задает, требуется ли ожидание всех
                      // объектов или любого
   dwMilliseconds,    // Период ожидания
   dwWakeMask: DWORD  // Тип события, прерывающего ожидание
 ): DWORD; stdcall;  

Главное отличие этой функции от
предыдущей — параметр dwWakeMask,
который является комбинацией битовых
флагов QS_XXX
и задает типы сообщений, прерывающих
ожидание функции независимо от состояния
ожидаемых объектов. Например, маска QS_KEY
позволяет прервать ожидание при появлении
в очереди сообщений WM_KEYUP,
WM_KEYDOWN,
WM_SYSKEYUP
или WM_SYSKEYDOWN,
а маска QS_PAINT
— сообщения WM_PAINT.
Полный список значений, допустимых для dwWakeMask,
имеется в документации по Windows
SDK. При
появлении в очереди потока, вызвавшего
функцию, сообщений, соответствующих
заданной маске, функция возвращает
значение WAIT_OBJECT_0
+ nCount.
Получив это значение, ваша программа может
обработать его и снова вызвать функцию
ожидания. Рассмотрим пример с запуском
внешнего приложения (необходимо, чтобы на время его работы
вызывающая программа не реагировала на
ввод пользователя, однако ее окно должно
продолжать перерисовываться):

procedure TForm1.Button1Click(Sender: TObject);
 var
   PI: TProcessInformation;
   SI: TStartupInfo;
   Reason: DWORD;
   Msg: TMsg;
 begin
   // Инициализируем структуру TStartupInfo
   FillChar(SI, SizeOf(SI), 0);
   SI.cb := SizeOf(SI);
   // Запускаем внешнюю программу
   Win32Check(CreateProcess(NIL, 'COMMAND.COM', NIL,
     NIL, FALSE, 0, NIL, NIL, SI, PI));
 //**************************************************
 // Попробуйте заменить нижеприведенный код на строку
 // WaitForSingleObject(PI.hProcess, INFINITE);
 // и посмотреть, как будет реагировать программа на
 // перемещение других окон над ее окном
 //**************************************************
   repeat
     // Ожидаем завершения дочернего процесса или сообщения
     // перерисовки WM_PAINT
     Reason := MsgWaitForMultipleObjects(1, PI.hProcess, FALSE,
       INFINITE, QS_PAINT);
     if Reason = WAIT_OBJECT_0 + 1 then begin
       // В очереди сообщений появился WM_PAINT – Windows
       // требует обновить окно программы.
       // Удаляем сообщение из очереди
       PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE);
       // И перерисовываем наше окно
       Update;
     end;
     // Повторяем цикл, пока не завершится дочерний процесс
   until Reason = WAIT_OBJECT_0;
   // Удаляем из очереди накопившиеся там сообщения
   while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do;
   CloseHandle(PI.hProcess);
   CloseHandle(PI.hThread)
 end;  

Если в потоке, вызывающем функции
ожидания, явно (функцией CreateWindow)
или неявно (используя TForm, DDE, COM)
создаются окна Windows
— поток должен
обрабатывать сообщения. Поскольку
широковещательные сообщения посылаются
всем окнам в системе, то поток, не
обрабатывающий сообщения, может вызвать
взаимоблокировку (система ждет, когда поток
обработает сообщение, поток — когда
система или другие потоки освободят объект)
и привести к зависанию Windows.
Если в вашей программе имеются подобные
фрагменты, необходимо использовать MsgWaitForMultipleObjects
или MsgWaitForMultipleObjectsEx
и позволять прервать ожидание для
обработки сообщений. Алгоритм
аналогичен вышеприведенному примеру.

Прерывание ожидания по запросу на завершение операции ввода-вывода
или APC

Windows
поддерживает асинхронные вызовы процедур.
При создании каждого потока (thread) с ним
ассоциируется очередь асинхронных вызовов
процедур (APC queue). Операционная
система (или приложение пользователя — при
помощи функции QueueUserAPC)
может помещать в нее запросы на выполнение
функций в контексте данного потока. Эти
функции не могут быть выполнены немедленно,
поскольку поток может быть занят. Поэтому
операционная система вызывает их, когда
поток вызывает одну из следующих функций
ожидания:

function SleepEx(
   dwMilliseconds: DWORD;   // Период ожидания
   bAlertable: BOOL         // задает, должна ли функция возвращать
                            // управление в случае запроса на
                            // асинхронный вызов процедуры
 ): DWORD; stdcall;
 
   
function WaitForSingleObjectEx(
   hHandle: THandle;      // Идентификатор объекта
   dwMilliseconds: DWORD; // Период ожидания
   bAlertable: BOOL       // задает, должна ли функция возвращать
                          // управление в случае запроса на
                          // асинхронный вызов процедуры
 ): DWORD; stdcall;
 
   
function WaitForMultipleObjectsEx(
   nCount: DWORD;            // количество объектов
   lpHandles: PWOHandleArray;// адрес массива идентификаторов объектов
   bWaitAll: BOOL;           // Задает, требуется ли ожидание всех
                             // объектов или любого
   dwMilliseconds: DWORD;    // Период ожидания
   bAlertable: BOOL          // задает, должна ли функция возвращать
                             // управление в случае запроса на
                             // асинхронный вызов процедуры
 ): DWORD; stdcall;
 
   
function SignalObjectAndWait(
   hObjectToSignal: THandle;  // объект, который будет переведен в
                              // сигнальное состояние
   hObjectToWaitOn: THandle;  // объект, которого ожидает функция
   dwMilliseconds: DWORD;     // период ожидания
   bAlertable: BOOL           // задает, должна ли функция возвращать
                              // управление в случае запроса на
                              // асинхронный вызов процедуры
 ): DWORD; stdcall;  
   
function MsgWaitForMultipleObjectsEx(
 nCount: DWORD;     // количество объектов синхронизации
   var pHandles;      // адрес массива объектов
   fWaitAll: BOOL;    // Задает, требуется ли ожидание всех
                      // объектов или любого
   dwMilliseconds,    // Период ожидания
   dwWakeMask: DWORD  // Тип события, прерывающего ожидание
   dwFlags: DWORD     // Дополнительные флаги
 ): DWORD; stdcall;  

Если параметр bAlertable
равен TRUE
(либо если dwFlags
в функции MsgWaitForMultipleObjectsEx
содержит MWMO_ALERTABLE),
то при появлении в очереди APC
запроса на
асинхронный вызов процедуры операционная
система выполняет вызовы всех имеющихся в
очереди процедур, после чего функция
возвращает значение WAIT_IO_COMPLETION.

Такой
механизм позволяет реализовать, например,
асинхронный ввод-вывод. Поток может
инициировать фоновое выполнение одной или
нескольких операций ввода-вывода функциями
ReadFileEx или WriteFileEx, передав им адреса функций-обработчиков
завершения операции. По завершении вызовы
этих функций будут поставлены в очередь
асинхронного вызова процедур. В свою
очередь, инициировавший операции поток,
когда он будет готов обработать результаты,
может, используя одну из вышеприведенных
функций ожидания, позволить операционной
системе вызвать функции-обработчики. Поскольку
очередь APC
реализована на уровне ядра ОС, она более
эффективна, чем очередь сообщений, и
позволяет реализовать гораздо более
эффективный ввод-вывод.

Объекты синхронизации

Объектами
синхронизации называются объекты Windows,
идентификаторы которых могут
использоваться в функциях синхронизации. Они делятся на две группы: объекты,
использующиеся только для синхронизации, и
объекты, которые используются в других
целях, но могут вызывать срабатывание
функций ожидания. К первой
группе относятся:

Event (событие)

Event
позволяет
известить один или несколько ожидающих
потоков о наступлении события. Event
бывает:

Отключаемый
вручную

Будучи
установленным в сигнальное состояние,
остается в нем до тех пор, пока не будет
переключен явным вызовом функции ResetEvent

Автоматически
отключаемый

Автоматически
переключается в несигнальное состояние
операционной системой, когда один из
ожидающих его потоков завершается

Для создания объекта используется функция CreateEvent:

function CreateEvent(
   lpEventAttributes: PSecurityAttributes;  // Адрес структуры
                                            // TSecurityAttributes
    bManualReset,         // Задает, будет Event переключаемым
                          // вручную (TRUE) или автоматически (FALSE)
    bInitialState: BOOL;  // Задает начальное состояние. Если TRUE -
                          // объект в сигнальном состоянии
    lpName: PChar         // Имя или NIL, если имя не требуется
 ): THandle; stdcall;     // Возвращает идентификатор созданного
                          // объекта  
Структура TSecurityAttributes описана, как:  
TSecurityAttributes = record
   nLength: DWORD;                // Размер структуры, должен
                                  // инициализироваться как
                                  // SizeOf(TSecurityAttributes)
   lpSecurityDescriptor: Pointer; // Адрес дескриптора защиты. В
                                  // Windows 95 и 98 игнорируется
                                  // Обычно можно указывать NIL
   bInheritHandle: BOOL;          // Задает, могут ли дочерние
                                  // процессы наследовать объект
 end;  

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

Параметр
lpName позволяет разделять объекты между
процессами. Если lpName совпадает с именем уже существующего
объекта типа Event,
созданного текущим или любым другим
процессом, то функция не
создает нового объекта, а возвращает
идентификатор уже существующего. При этом
игнорируются параметры bManualReset, bInitialState и lpSecurityDescriptor.
Проверить, был ли объект создан или
используется уже существующий, можно
следующим образом:

hEvent := CreateEvent(NIL, TRUE, FALSE, ‘EventName’);
 if hEvent = 0 then
   RaiseLastWin32Error;
 if GetLastError = ERROR_ALREADY_EXISTS then begin
   // Используем ранее созданный объект
 end;  

Если объект используется для
синхронизации внутри одного процесса, его
можно объявить как глобальную переменную и
создавать без имени.

Имя
объекта не должно совпадать с именем любого
из существующих объектов типов Semaphore, Mutex, Job,
Waitable Timer или FileMapping. В случае совпадения имен функция
возвращает ошибку.

Если известно, что Event
уже создан, для получения доступа к нему
можно вместо CreateEvent воспользоваться функцией OpenEvent:

function OpenEvent(
   dwDesiredAccess: DWORD;  // Задает права доступа к объекту
   bInheritHandle: BOOL;    // Задает, может ли объект наследоваться
                            // дочерними процессами
   lpName: PChar            // Имя объекта
 ): THandle; stdcall;  

Функция возвращает идентификатор
объекта либо 0 — в случае ошибки. Параметр
dwDesiredAccess может принимать одно из следующих
значений:

EVENT_ALL_ACCESS              

Приложение
получает полный доступ к объекту

EVENT_MODIFY_STATE              

Приложение
может изменять состояние объекта
функциями SetEvent и ResetEvent

SYNCHRONIZE              

Только для Windows
NT
— приложение может использовать объект
только в функциях ожидания

После получения идентификатора можно
приступать к его использованию. Для
этого имеются следующие функции:

function SetEvent(hEvent: THandle): BOOL; stdcall;  

— устанавливает объект в сигнальное состояние

function ResetEvent(hEvent: THandle): BOOL; stdcall;  

— сбрасывает объект, устанавливая его в несигнальное состояние

function PulseEvent(hEvent: THandle): BOOL; stdcall  

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

В Windows API
события используются для выполнения
операций асинхронного ввода-вывода.
Следующий пример показывает, как
приложение инициирует запись одновременно
в два файла, а затем ожидает завершения
записи перед продолжением работы; такой подход
может обеспечить более высокую
производительность при высокой
интенсивности ввода-вывода, чем
последовательная запись:

var
   Events: array[0..1] of THandle;  // Массив объектов синхронизации
   Overlapped: array[0..1] of TOverlapped;
 
 ...
 
 // Создаем объекты синхронизации
 Events[0] := CreateEvent(NIL, TRUE, FALSE, NIL);
 Events[1] := CreateEvent(NIL, TRUE, FALSE, NIL);
 
 // Инициализируем структуры TOverlapped
 FillChar(Overlapped, SizeOf(Overlapped), 0);
 Overlapped[0].hEvent := Events[0];
 Overlapped[1].hEvent := Events[1];
 
 // Начинаем асинхронную запись в файлы
 WriteFile(hFirstFile, FirstBuffer, SizeOf(FirstBuffer),
   FirstFileWritten, @Overlapped[0]);
 WriteFile(hSecondFile, SecondBuffer, SizeOf(SecondBuffer),
   SecondFileWritten, @Overlapped[1]);
 
 // Ожидаем завершения записи в оба файла
 WaitForMultipleObjects(2, @Events, TRUE, INFINITE);
 
 // Уничтожаем объекты синхронизации
 CloseHandle(Events[0]);
 CloseHandle(Events[1]) 

По завершении работы с объектом он должен
быть уничтожен функцией CloseHandle.

Delphi
предоставляет класс TEvent,
инкапсулирующий функциональность объекта Event.
Класс расположен в модуле
SyncObjs.pas и объявлен следующим образом:

type
   TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError);
 
   TEvent = class(THandleObject)
   public
     constructor Create(EventAttributes: PSecurityAttributes;
       ManualReset, InitialState: Boolean; const Name: string);
     function WaitFor(Timeout: DWORD): TWaitResult;
     procedure SetEvent;
     procedure ResetEvent;
   end;  

Назначение методов очевидно следует из
их названий. Использование
этого класса позволяет не вдаваться в
тонкости реализации вызываемых функций
Windows API. Для простейших случаев объявлен еще один
класс с упрощенным конструктором:

type
   TSimpleEvent = class(TEvent)
   public
     constructor Create;
   end;
 
 …
 
 constructor TSimpleEvent.Create;
 begin
   FHandle := CreateEvent(nil, True, False, nil);
 end;  

Mutex (Mutually Exclusive)

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

function CreateMutex(
   lpMutexAttributes: PSecurityAttributes;  // Адрес структуры
                                            // TSecurityAttributes
   bInitialOwner: BOOL;  // Задает, будет ли процесс владеть
                         // мьютексом сразу после создания
   lpName: PChar         // Имя мьютекса
 ): THandle; stdcall;  

Функция возвращает идентификатор
созданного объекта либо 0. Если мьютекс с
заданным именем уже был создан,
возвращается его идентификатор. В
этом случае функция GetLastError вернет код
ошибки ERROR_ALREDY_EXISTS. Имя
не должно совпадать с именем уже
существующего объекта типов Semaphore,
Event, Job,
Waitable
Timer или
FileMapping.

Если неизвестно, существует ли уже
мьютекс с таким именем, программа не должна
запрашивать владение объектом при создании
(то есть должна передать в качестве bInitialOwner значение FALSE).

Если мьютекс уже существует, приложение
может получить его идентификатор функцией OpenMutex:

function OpenMutex(
   dwDesiredAccess: DWORD;  // Задает права доступа к объекту
   bInheritHandle: BOOL;    // Задает, может ли объект наследоваться
                            // дочерними процессами
   lpName: PChar            // Имя объекта
 ): THandle; stdcall;  

Параметр dwDesiredAccess
может принимать одно из следующих значений:

MUTEX_ALL_ACCESS 
Приложение получает полный доступ к объекту
SYNCHRONIZE
Только для Windows NT — приложение может использовать объект только в
функциях ожидания и функции ReleaseMutex

Функция возвращает идентификатор
открытого мьютекса либо 0 — в случае ошибки.
Мьютекс переходит в сигнальное состояние
после срабатывания функции ожидания, в
которую был передан его идентификатор. Для
возврата в несигнальное состояние служит
функция ReleaseMutex:

function ReleaseMutex(hMutex: THandle): BOOL; stdcall;  

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

var
   Mutex: THandle;
 
 // При инициализации программы
 Mutex := CreateMutex(NIL, FALSE, ‘UniqueMutexName’);
 if Mutex = 0 then
   RaiseLastWin32Error;
 
 ...
 // Доступ к ресурсу
 WaitForSingleObject(Mutex, INFINITE);
 try
   // Доступ к ресурсу, захват мьютекса гарантирует,
   // что остальные процессы, пытающиеся получить доступ,
   // будут остановлены на функции WaitForSingleObject
   ...
 finally
   // Работа с ресурсом окончена, освобождаем его
   // для остальных процессов
   ReleaseMutex(Mutex);
 end;
 
 ...
 // При завершении программы
 CloseHandle(Mutex);  

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

Разумеется,
если работа с ресурсом может потребовать
значительного времени, то необходимо либо
использовать функцию MsgWaitForSingleObject, либо
вызывать WaitForSingleObject в цикле с нулевым
периодом ожидания, проверяя код возврата. В
противном случае ваше
приложение окажется замороженным. Всегда
защищайте захват-освобождение объекта
синхронизации при помощи блока try … finally,
иначе ошибка во время работы с ресурсом
приведет к блокированию работы всех
процессов, ожидающих его освобождения.

Semaphore (семафор)

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

Для создания семафора служит функция CreateSemaphore:

function CreateSemaphore(
   lpSemaphoreAttributes: PSecurityAttributes; // Адрес структуры
                                               // TSecurityAttributes
   lInitialCount,           // Начальное значение счетчика
   lMaximumCount: Longint;  // Максимальное значение счетчика
   lpName: PChar            // Имя объекта
 ): THandle; stdcall;  

Функция возвращает идентификатор
созданного семафора либо 0, если создать
объект не удалось.

Параметр
lMaximumCount задает максимальное значение
счетчика семафора, lInitialCount задает начальное
значение счетчика и должен быть в диапазоне
от 0 до lMaximumCount. lpName задает имя семафора. Если
в системе уже есть семафор с таким именем,
то новый не создается, а возвращается
идентификатор существующего семафора. В
случае если семафор используется внутри
одного процесса, можно создать его без
имени, передав в качестве lpName значение NIL. Имя
семафора не должно совпадать с именем уже
существующего объекта типов event,
mutex, waitable
timer, job
или file-mapping.

Идентификатор ранее созданного семафора
может быть также получен функцией OpenSemaphore:

function OpenSemaphore(
     dwDesiredAccess: DWORD;  // Задает права доступа к объекту
     bInheritHandle: BOOL;    // Задает, может ли объект наследоваться
                                 // дочерними процессами
     lpName: PChar            //    Имя объекта
   ): THandle; stdcall; 

Параметр dwDesiredAccess
может принимать одно из следующих значений:

SEMAPHORE_ALL_ACCESS              

Поток
получает все права на семафор

SEMAPHORE_MODIFY_STATE              

Поток
может увеличивать счетчик семафора
функцией ReleaseSemaphore

SYNCHRONIZE              

Только для Windows
NT
— поток может использовать семафор в
функциях ожидания

Для увеличения счетчика семафора
используется функция ReleaseSemaphore:

function ReleaseSemaphore(
   hSemaphore: THandle;      // Идентификатор семафора
   lReleaseCount: Longint;   // Счетчик будет увеличен на эту величину
   lpPreviousCount: Pointer  // Адрес 32-битной переменной,
                             // принимающей предыдущее значение
                             // счетчика
 ): BOOL; stdcall;  

Если значение счетчика после выполнения
функции превысит заданный для него
функцией CreateSemaphore максимум, то ReleaseSemaphore
возвращает FALSE
и значение семафора не изменяется. В
качестве параметра lpPreviousCount можно передать
NIL, если это значение нам не нужно.

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

unit LimitedThread;
 
 interface
 
 uses Classes;
 
 type
   TLimitedThread = class(TThread)
     procedure Execute; override;
   end;
  
 implementation
 
 uses Windows;
 
 const
   MAX_THREAD_COUNT = 10;
 
 var
   Semaphore: THandle;
 
 procedure TLimitedThread.Execute;
 begin
   // Уменьшаем счетчик семафора. Если к этому моменту уже запущено
   // MAX_THREAD_COUNT потоков — счетчик равен 0 и семафор в
   // несигнальном состоянии. Поток будет заморожен до завершения
   // одного из запущенных ранее.
   WaitForSingleObject(Semaphore, INFINITE);
  
   // Здесь располагается код, отвечающий за функциональность потока,
   // например загрузка файла
   ...
 
   // Поток завершил работу, увеличиваем счетчик семафора и позволяем
   // начать обработку другим потокам.
   ReleaseSemaphore(Semaphore, 1, NIL);
 end;
 
 initialization
   // Создаем семафор при старте программы
   Semaphore := CreateSemaphore(NIL, MAX_THREAD_COUNT,
     MAX_THREAD_COUNT, NIL);
 
 finalization
   // Уничтожаем семафор по завершении программы
   CloseHandle(Semaphore);
 end;  

Понравилась статья? Поделить с друзьями:
  • Система windows защитила ваш компьютер исключения
  • Синхронизация папок на разных дисках в windows 10
  • Система windows защитила ваш компьютер выполнить в любом случае
  • Синхронизация папок в windows server 2008
  • Система windows заблокировала установку драйвера без цифровой подписи windows 10