Министерство цифрового развития, связи и массовых коммуникаций Российской Федерации
Федеральное государственное бюджетное образовательное учреждение Высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. Проф. М. А. Бонч-Бруевича» (СПбГУТ)
Факультет Информационных технологий и программной инженерии
Кафедра Программной инженерии
Лабораторная работа №3
По дисциплине: Операционные системы и сети
Выполнил студент:
Яковлев М. А. ИКПИ-32
Принял работу:
Дагаев А. В.
Дата выполнения:
«05» ноября 2025 г.
Санкт-Петербург
2025 г.
Постановка задачи
Необходимо разработать многопоточный алгоритм, моделирующий работу алгоритма «Читатели-Писатели». Программа должна обеспечивать синхронизацию доступа к общему ресурсу (хранилищу данных) между несколькими потоками-читателями и потоками-писателями. Используются семафоры.
Семафор — это специальная переменная или объект в программировании, используемый для контроля доступа к общим (разделяемым) ресурсам, таким как память, файлы или устройства. Он особенно важен при многопоточном и параллельном программировании. Семафор работает как контролёр, разрешая или блокируя доступ к ресурсу, чтобы избежать конфликтов и ошибок при одновременном доступе нескольких потоков.
Перечень функций
В коде используются следующие функции:
TimeStats::addDuration — добавляет значение времени выполнения операции в вектор для статистики.
Класс FastSemaphore:
FastSemaphore(long initial) — конструктор, инициализирует счётчик и создаёт событие Windows.
~FastSemaphore() — деструктор, закрывает событие.
wait() — пытается атомарно уменьшить счётчик, при его отсутствии ждёт сигнала события.
signal() — увеличивает счётчик и пробуждает поток через событие, если требуется.
Функции-потоки с FastSemaphore:
writerFastFunc — цикл операций записи с ожиданием и сигналом семафора, измеряет время выполнения.
readerFastFunc — цикл операций чтения с аналогичным контролем и замером времени.
Функции-потоки с std::counting_semaphore:
writerStdFunc — операция записи с использованием встроенного семафора.
readerStdFunc — операция чтения с использованием встроенного семафора.
main — точка входа, где
Инициализируются параметры тестов, создаются потоки для обоих видов семафора.
Запускаются и дожидаются завершения потоков.
Собранные параметры времени сохраняются в CSV-файлы для дальнейшего анализа.
Вся логика построена вокруг синхронизации потоков-писателей и читателей с использованием двух видов семафоров, а также измерения и записи времени для сравнения производительности.
Описание алгоритма
Программа моделирует классическую задачу синхронизации «Читатели-Писатели» с приоритетом писателей. Алгоритм работает следующим образом:
Алгоритм работы данного кода построен на синхронизации потоков с помощью критических секций Windows для безопасного доступа к общему ресурсу — целочисленному хранилищу. Изначально в главной функции инициализируются три критические секции: для защиты операций записи в хранилище, для защиты флага блокировки чтения и для безопасного логирования. Создаются два типа потоков — писатели и читатели, а также отдельный мониторящий поток, который наблюдает за завершением работы всех потоков.
Поток-писатель при каждой итерации пытается войти в критическую секцию записи, сигнализируя о намерении выполнить запись. После захвата критической секции он помечает, что чтение в данный момент запрещено, и ждет, пока все активные читатели завершат свои операции, отслеживая счётчик активных читателей. Затем он выполняет имитацию записи, обновляя общее хранилище, и освобождает блокировки, позволяя другим потокам работать с ресурсом.
Поток-читатель в начале каждой операции ждет, пока не будет снят запрет на чтение, установленный писателями, проверяя специальный флаг внутри критической секции. После того как доступ к чтению разрешен, он увеличивает счетчик активных читателей, читает значение из хранилища и затем уменьшает счетчик. Все операции тесно вынуждены выполнять синхронизированно, чтобы избежать одновременного изменения данных.
Мониторный поток периодически проверяет сколько писателей и читателей выполнили все свои операции и выводит информацию о текущем прогрессе. Как только все потоки завершают работу, монитор сигнализирует о завершении программы.
График
График, построенный на основе полученной статистики.
Рисунок 1. График временных затрат
На графике (рис. 1) представлены временные затраты на работу двух видов семафоров: пользовательского и встроенного (стандартного). Видно, что пользовательский семафор имеет немного меньшую задержку в сравнении со встроенным. Однако эффективность пользовательского семафора начинает превосходить стандартный только при больших объёмах данных.
Демонстрационная таблица 1.
Таблица 2. Описание первых нескольких шагов
Время |
Роль |
Сообщение |
Шаг 1 |
Писатель 0 |
обратился к хранилищу... |
Шаг 2 |
Писатель 0 |
получил доступ к хранилищу. |
Шаг 3 |
Писатель 0 |
записал в хранилище сообщение 18467 и закончил работу. |
Шаг 4 |
Читатель 0 |
обратился к хранилищу... |
Шаг 5 |
Читатель 0 |
получил доступ к хранилищу. |
Шаг 6 |
Писатель 1 |
обратился к хранилищу... |
Шаг 7 |
Писатель 1 |
ожидает, пока активные читатели закончат работу с хранилищем. |
Шаг 8 |
Читатель 0 |
прочитал из хранилища 18467 и закончил работу. |
Шаг 9 |
Писатель 1 |
получил доступ к хранилищу. |
Шаг 9 |
Писатель 1 |
записал в хранилище сообщение 18468 и закончил работу. |
Рисунок 1. Схема работы первых шагов
В рамках последовательности временных отметок зафиксированы взаимодействия между участниками системы хранения данных. Сначала Писатель 0 обращается к хранилищу и получает доступ, после чего записывает в хранилище сообщение с идентификатором 18467 и завершает работу. Затем Читатель 0 обращается к хранилищу и получает доступ, последующим шагом Писатель 1 обращается к хранилищу и ожидает, пока активные читатели закончат работу. В более поздний момент Читатель 0 читает из хранилища сообщение 18467 и завершает работу. После этого Писатель 1 снова получает доступ к хранилищу, записывает сообщение 18468 и завершает операцию. Общая картина демонстрирует последовательность чтения и записи с синхронизацией между читателями и писателями, включая ожидание завершения текущих операций перед продолжением записи новыми сообщениями.
Демонстрационная таблица 2.
Таблица 2. Описание шагов
Время |
Роль |
Действие |
Сообщение |
Шаг 1 |
Писатель 0 |
Обратился |
Обратился к хранилищу... |
Шаг 2 |
Читатель 0 |
Обратился |
Обратился к хранилищу... |
Шаг 3 |
Писатель 0 |
Получил |
Получил доступ к хранилищу. |
Шаг 4 |
Писатель 1 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 5 |
Читатель 0 |
Получил |
Получил доступ к хранилищу. |
Шаг 6 |
Читатель 1 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 7 |
Писатель 0 |
Записал |
Записал в хранилище сообщение 18467 и освободил ресурс. |
Шаг 8 |
Читатель 0 |
Прочитал |
Прочитал из хранилища 18467 и освободил ресурс. |
Шаг 9 |
Писатель 1 |
Получил |
Получил доступ к хранилищу. |
Шаг 10 |
Писатель 2 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 11 |
Читатель 1 |
Получил |
Получил доступ к хранилищу. |
Шаг 12 |
Писатель 1 |
Записал |
Записал в хранилище сообщение 18468 и освободил ресурс. |
Шаг 13 |
Читатель 1 |
Прочитал |
Прочитал из хранилища 18468 и освободил ресурс. |
Шаг 14 |
Читатель 2 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 15 |
Писатель 3 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 16 |
Читатель 2 |
Получил |
Получил доступ к хранилищу. |
Шаг 17 |
Писатель 2 |
Получил |
Получил доступ к хранилищу. |
Шаг 18 |
Читатель 2 |
Прочитал |
Прочитал из хранилища 18468 и освободил ресурс. |
Шаг 19 |
Писатель 2 |
Записал |
Записал в хранилище сообщение 18469 и освободил ресурс. |
Шаг 20 |
Писатель 3 |
Получил |
Получил доступ к хранилищу. |
Шаг 21 |
Читатель 3 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 22 |
Писатель 3 |
Записал |
Записал в хранилище сообщение 18470 и освободил ресурс. |
Шаг 23 |
Читатель 3 |
Получил |
Получил доступ к хранилищу. |
Шаг 24 |
Читатель 3 |
Прочитал |
Прочитал из хранилища 18470 и освободил ресурс. |
Шаг 25 |
Писатель 4 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 26 |
Читатель 4 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 27 |
Писатель 4 |
Получил |
Получил доступ к хранилищу. |
Шаг 28 |
Читатель 4 |
Получил |
Получил доступ к хранилищу. |
Шаг 29 |
Писатель 4 |
Записал |
Записал в хранилище сообщение 18471 и освободил ресурс. |
Шаг 30 |
Читатель 4 |
Прочитал |
Прочитал из хранилища 18471 и освободил ресурс. |
Шаг 31 |
Писатель 5 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 32 |
Читатель 5 |
Обратился |
Обратился к хранилищу и ожидает доступа. |
Шаг 33 |
Писатель 5 |
Получил |
Получил доступ к хранилищу. |
Шаг 34 |
Читатель 5 |
Получил |
Получил доступ к хранилищу. |
Шаг 35 |
Писатель 5 |
Записал |
Записал в хранилище сообщение 18472 и освободил ресурс. |
Шаг 36 |
Читатель 5 |
Прочитал |
Прочитал из хранилища 18472 и освободил ресурс. |
Шаг 1-2: Писатель 0 и читатель 0 одновременно обратились к общему хранилищу, пытаясь получить к нему доступ.
Шаг 3: Писатель 0 получил доступ и стал работать, остальные, включая писателя 1 и читателя 1, продолжают ожидать.
Шаг 4-6: Писатель 1 и читатель 1 обратились к хранилищу и вынуждены ожидать освобождения, так как ресурс занят.
Шаг 7-8: Писатель 0 завершил запись, а читатель 0 прочитал данные, освобождая ресурс.
Шаг 9-13: Писатель 1 получает доступ и выполняет запись, читатель 1 затем получает доступ и читает, оба завершают работу.
Шаг 14-20: Вновь другие потоки (писатель 2, читатель 2, писатель 3) обращаются к хранилищу, часть из них ожидает освобождения ресурса, другие получают доступ, выполняют операции записи или чтения и освобождают доступ.
Шаг 21-36: Аналогично, оставшиеся писатели и читатели (4 и 5) поочередно обращаются, ждут, получают доступ, выполняют операции и завершают работу.
В каждом шаге происходит динамическое чередование состояний "обратился", "ожидает доступа", "получил доступ", "выполнил операцию (записал/прочитал)" и освобождения ресурса для других потоков.
