В отличие от мьютексов и критических секций, семафоры не имеют владельцев. У них есть только счётчики.
Функция ReleaseSemaphore увеличивает значение счётчика, связанного с семафором, на указанное число (это увеличение может освободить ожидающие потоки). Но поток, который освобождает семафор не обязан быть тем же самым, который изначально запросил семафор. Это отличается от мьютексов и критических секций, которые требуют, чтобы поток, который просит объект, и освобождал бы его.
Некоторые люди используют семафоры в стиле мьютексов: они создают семафор с начальным значением 1 и используют его примерно так:
WaitForSingleObject(hSemaphore, INFINITE); try // ... что-то делаем .. finally ReleaseSemaphore(hSemaphore, 1, nil); end;Если поток выходит (или вылетает) до того, как он отпускает семафор, то счётчик семафора не будет автоматически исправлен. Сравните это поведение с мьютексами, где мьютекс освобождается автоматически, если поток-владелец неожиданно завершается, пока он удерживает мьютекс. По этой причине для указанного выше шаблона предпочтителен мьютекс.
Семафор полезен для концепции владения ресурсом между потоками.
WaitForSingleObject(hSemaphore, INFINITE); // ... Что-то сделать .. // ... Продолжить на фоновом потоке ... hThread := CreateThread(nil, 0, KeepWorking, ...); if hThread = 0 then // ... отменить работу ... ReleaseSemaphore(hSemaphore, 1, nil); // освободить ресурсы function KeepWorking(Arg: Pointer): Cardinal; stdcall; begin // ... завершение работы ... ReleaseSemaphore(hSemaphore, 1, nil); Result := 0; end;Такой трюк не сработает с мьютексом или критической секцией, потому что они имеют владельцев, и только владелец может отпустить мьютекс или критическую секцию.
Замечу, что если функция KeepWorking выходит, забывая освободить семафор, то счётчик автоматически не меняется. Операционная система не знает, что семафор "принадлежит" этому коду.
Ещё одним частым шаблоном использования семафора является противоположность шаблону защиты ресурса: это шаблон генерации ресурсов. В этой модели счётчик семафоров обычно ноль, но он увеличивается каждый раз, когда у вас есть работа, которую нужно выполнить.
// ... задаём задачу для выполнения и заносим её в список ... ReleaseSemaphore(hSemaphore, 1, nil); // У вас может быть более одного рабочего потока. // Каждый раз, когда семафор сигнализирует о новом элементе, один из потоков будет выбран для его обработки. function ProcessWork(Arg: Pointer): Cardinal; stdcall; label Loop; begin Loop: // Ждём появления работы для выполнения WaitForSingleObject(hSemaphore, INFINITE); // ... извлекаем задачу из списка ... // ... выполняем работу ... goto Loop; // Сюда никогда не попадаем end;Заметьте, что в этом случае у вас нет даже концептуального "владельца" семафора. Если функция ProcessWork завершается, вы бы точно не хотели, чтобы счётчик семафора изменялся бы автоматически, потому что это нарушило бы учёт задач. Поэтому, семафор будет подходящим объектом для этого сценария.
(порт завершения ввода-вывода - это более высоко-производительная версия семафора вида producer/consumer)
Посмотрим, как вы, вооружённые этой информацией, сможете ответить на вопрос этого человека.
Хорошая статья!
ОтветитьУдалить