Синхронизация потоков в операционной системе Windows, страница 7

Листинг 1.

#include <windows.h>

#include <iostream.h>

void main(int argc, char *argv[])

{

          // создаем или открываем имеющееся

          HANDLE ev=CreateEvent(NULL, FALSE, FALSE, "nt5bbevent");

          if (argc==1)

          {

                    cout<<"Подождите событие!\n";

                    cout.flush();

                    WaitForSingleObject(ev, INFINITE);

                    cout<<"Событие произошло!\n";

                    cout.flush();

          }

          else

          {

                    cout<<"Установка события\n";

                    SetEvent(ev);

          }

          CloseHandle(ev);

}

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

 Подробнее о мьютексах

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

Вызовы для работы с мьютексами                                           Таблица 3.

Вызов

Предназначение

CreateMutex OpenMutex ReleaseMutex

Создает новый мьютекс или открывает уже существующий

Открывает существующий мьютекс

Освобождает мьютекс и делает его доступным для других потоков

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

Предположим, поток А обращается к Е2, а чуть позже поток В обращается к Е1. В этой ситуации поток В обязан подождать, пока мьютекс освободится. В соответствии с логикой вещей так и должно быть. Что происходит при выполнении потока А? Функция Е2 открывает мьютекс, выполняет некоторые действия, после чего обращается к функции Е1. Если бы поток не обладал возможностью повторно завладеть мьютексом, он не смог бы продолжить работу. Ведь функция Е1 попыталась бы завладеть мьютексом, который уже принадлежит потоку (поток А завладел мьютексом в процессе выполнения функции Е2).

К счастью, Windows обрабатывает подобные ситуации корректно. Когда Е1 от имени потока А пытается завладеть мьютексом, операционная система обнаруживает, что мьютекс уже принадлежит этому потоку, поэтому функции Е1 разрешается завладеть мьютексом повторно. Когда Е1 освобождает мьютекс, Windows понимает, что функция Е2 все еще нуждается в мьютексе, поэтому право потока А на владение мьютексом сохраняется до тех пор, пока функция Е2 не освободит мьютекс.