Как стать автором
Обновить

Семафор на событиях C++

Время на прочтение3 мин
Количество просмотров22K
Сегодня коротко расскажу о том, как я реализовывал семафор на основании объекта синхронизации «Событие».

Сначала пройдусь по определениям.

1. Что такое синхронизация и зачем она нужна?


Очевидно, что набор действий мы можем выполнять несколькими способами. Самые простые — последовательно и параллельно. Параллельности выполнения определенных действий можно достигнуть за счет запуска различных потоков (threads). Идея простая: назначаем каждому потоку какое-то элементарное (или не очень) действие и запускаем их в определенном порядке. Вообще говоря, запустить мы их можем и все одновременно — выигрыш по времени мы, конечно, получим. Это понятно: одно дело вывести 10 000 слов одно за другим, а другое дело одновременно выводить, например, 100 слов. 100-кратный выигрыш по времени (плюс-минус, без учета задержек и проч.). Но исходная задача может предполагать строгую последовательность действий.

Например:

  • Открыть файл
  • Записать текст в файл
  • Закрыть файл

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

  • Сгенерировать три последовательности случайных чисел
  • Последовательно вывести их на экран

Здесь первый пункт можно выполнять одновременно тремя разными потоками, а вот последний, вывод, нужно делать последовательно, причем только после отработки первого пункта.

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

2. Инструменты для синхронизации потоков


В windows.h реализовано достаточно много штатных инструментов синхронизации (так называемые «объекты синхронизации»). Среди основных: критическая область, событие, мьютекс, семафор. Да-да, для семафора уже есть реализация в windows.h. «Так зачем же его программировать?» — спросите Вы. Ну, во-первых, чтобы лучше прочувствовать, как он устроен. И, во-вторых, лишняя практика C++ никому еще не помешала :)

Учитывая, что мы будем использовать События, поясним, что это и как это использовать.

События используют, чтобы уведомлять ожидающие потоки. Т.е., фактически, это некоторый сигнал для потока — можно сработать или пока еще нет. Из самого смысла этого объекта вытекает, что он обладает некоторым сигнальным состоянием и возможностью его регулировки (сброс/«включение»).

Итак, после подключения windows.h мы можем создать событие с помощью:

HANDLE CreateEvent
(
	LPSECURITY_ATTRIBUTES lpEventAttributes,	// атрибуты защиты
	BOOL bManualReset,				// тип сброса: TRUE - ручной
	BOOL bInitialState,			// начальное состояние: TRUE - сигнальное
	LPCTSTR lpName				// имя объекта
);

Если функция завершилась успешно, то вернется дескриптор события. Если объект не удалось создать, вернется NULL.

Чтобы поменять состояние события на сигнальное, воспользуемся функцией:

BOOL SetEvent
(
	HANDLE hEvent		// дескриптор события
);

В случае успеха вернет ненулевое значение.

Теперь про семафор. Семафор призван регулировать количество одновременно запущенных потоков. Допустим, у нас 1000 потоков, но одновременно могут работать только 2. Вот такого типа регулировка и происходит с помощью семафора. А какие функции реализованы для работы с этим объектом синхронизации?

Для создания семафора, по аналогии с event-ами:

HANDLE CreateSemaphore
(
	LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,	// атрибуты доступа
	LONG lInitialCount,			// инициализированное начальное состояние счетчика
	LONG lMaximumCount,			// максимальное количество обращений
	LPCTSTR lpName				// имя объекта
);

При успешном выполнении получим указатель на семафор, при неудаче — NULL.

Счетчик семафора у нас постоянно меняется (поток выполняется и появляется вакантное место), поэтому состояние семафора нужно периодически менять. Делается это с помощью этой функции:

BOOL ReleaseSemaphore
(
	HANDLE hSemaphore,		// указатель на семафор
	LONG lReleaseCount, 	// на сколько изменять счетчик
	LPLONG lpPreviousCount	// предыдущее значение
);

В случае успеха возвращаемое значение — не ноль.

Также стоит обратить внимание на функцию:

DWORD WaitForSingleObject(
  HANDLE hHandle, // указатель на объект, отклик от которого ожидаем
  DWORD  dwMilliseconds // время ожидания в миллисекундах
);

Из возвращаемых значений нас особенно интересуют 2: WAIT_OBJECT_0 — значит, что состояние нашего объекта сигнальное; WAIT_TIMEOUT — сигнального состояния от объекта за отведенное время мы не дождались.

3. Непосредственно задача


Итого, наше задание заключается в том, чтобы написать свои аналоги на штатные функции. Не будем сильно усложнять задачу, сделаем «реализацию в первом приближении». Главное, сохранить количественные характеристики стандартного семафора. Код с комментариями можно найти на GitHub.

В силу простоты самой задачи, особо усложнять статью не будем, но кому-то может пригодиться :)
Теги:
Хабы:
Всего голосов 16: ↑4 и ↓12-4
Комментарии8

Публикации

Истории

Работа

QT разработчик
9 вакансий
Программист C++
127 вакансий

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань