Лабораторные. Дагаев / Операционные_системы_ЛР_№2_Отчет
.docxМинистерство цифрового развития, связи и массовых коммуникаций Российской Федерации
Федеральное государственное бюджетное образовательное учреждение Высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. Проф. М. А. Бонч-Бруевича» (СПбГУТ)
Факультет Информационных технологий и программной инженерии
Кафедра Программной инженерии
Лабораторная работа №2
По дисциплине: Операционные системы и сети
Выполнил студент:
Яковлев М. А. ИКПИ-32
Принял работу:
Дагаев А. В.
Дата выполнения:
«31» октября 2025 г.
Санкт-Петербург
2025 г.
Постановка задачи
Необходимо разработать многопоточный алгоритм, моделирующий работу алгоритма «Читатели-Писатели». Программа должна обеспечивать синхронизацию доступа к общему ресурсу (хранилищу данных) между несколькими потоками-читателями и потоками-писателями.
Перечень функций
printTimeStamp — Формирование и вывод временной метки с миллисекундной точностью
logPrint — Потокобезопасное логирование сообщений с временными метками
writer — Создание и управление потоками-писателями с синхронизацией доступа
reader — Создание и управление потоками-читателями с контролем блокировок
main — Точка входа программы, инициализация и запуск многопоточной системы
GetLocalTime — Получение системного времени с миллисекундной точностью (WinAPI)
Sleep — Приостановка выполнения потока на заданное время
Std::lock_guard — Автоматическое управление блокировками мьютекса
Std::mutex::try_lock — Попытка захвата мьютекса без блокировки потока
Atomic::load — Атомарное чтение значения из общей переменной
Atomic::store — Атомарная запись значения в общую переменную
Atomic::fetch_add — Атомарное увеличение значения с возвратом предыдущего
Atomic::fetch_sub — Атомарное уменьшение значения с возвратом предыдущего
Fopen — Открытие файла для ведения логов
Fprintf — Форматированный вывод в файл
Fflush — Принудительная запись буфера файла на диск
Fclose — Закрытие файла логов
Srand — Инициализация генератора случайных чисел
Rand — Генерация псевдослучайных чисел для временных задержек
Std::setlocale — Установка русской локали для корректного вывода
Std::thread::join — Ожидание завершения работы потоков
Std::vector::emplace_back — Создание потоков в контейнере без копирования
Описание алгоритма
Программа моделирует классическую задачу синхронизации «Читатели-Писатели» с приоритетом писателей. Алгоритм работает следующим образом:
1. Инициализация: Создаются общие ресурсы: storage – атомарная переменная для хранения данных; writeMutex – мьютекс для исключительной записи; readLock – флаг блокировки чтения при активной записи; activeReaders – счётчик активных читателей.
2. Потоки-писатели: Перед записью захватывают writeMutex. Устанавливают readLock = true, чтобы новые читатели не начинали чтение. Ждут, пока все активные читатели завершат чтение (activeReaders == 0). Выполняют запись в storage. Снимают readLock и освобождают writeMutex.
3. Потоки-читатели: Проверяют, не установлен ли readLock. Если запись не идёт, увеличивают activeReaders и начинают чтение. После чтения уменьшают activeReaders.
4. Логирование: Все действия потоков записываются в консоль и в файл log.txt с временными метками.
Таблица
Таблица, построенная на основе полученной статистики.
Таблица 1. Описание первых нескольких шагов
Время |
Роль |
Сообщение |
Шаг 1 |
Писатель 0 |
обратился к хранилищу... |
Шаг 2 |
Писатель 0 |
получил доступ к хранилищу. |
Шаг 3 |
Писатель 0 |
записал в хранилище сообщение 18467 и закончил работу. |
Шаг 4 |
Читатель 0 |
обратился к хранилищу... |
Шаг 5 |
Читатель 0 |
получил доступ к хранилищу. |
Шаг 6 |
Писатель 1 |
обратился к хранилищу... |
Шаг 7 |
Писатель 1 |
ожидает, пока активные читатели закончат работу с хранилищем. |
Шаг 8 |
Читатель 0 |
прочитал из хранилища 18467 и закончил работу. |
Шаг 9 |
Писатель 1 |
получил доступ к хранилищу. |
Шаг 9 |
Писатель 1 |
записал в хранилище сообщение 18468 и закончил работу. |
В рамках последовательности временных отметок зафиксированы взаимодействия между участниками системы хранения данных. Сначала Писатель 0 обращается к хранилищу и получает доступ, после чего записывает в хранилище сообщение с идентификатором 18467 и завершает работу. Затем Читатель 0 обращается к хранилищу и получает доступ, последующим шагом Писатель 1 обращается к хранилищу и ожидает, пока активные читатели закончат работу. В более поздний момент Читатель 0 читает из хранилища сообщение 18467 и завершает работу. После этого Писатель 1 снова получает доступ к хранилищу, записывает сообщение 18468 и завершает операцию. Общая картина демонстрирует последовательность чтения и записи с синхронизацией между читателями и писателями, включая ожидание завершения текущих операций перед продолжением записи новыми сообщениями.
Программа
Текст программы:
Рисунок 1. Программный файл
Продолжение программы:
Рисунок 2. Программный файл
Выходной файл:
Рисунок 3. Выходной файл
Вывод
В ходе лабораторной работы было разработано многопоточное приложение, реализующее алгоритм синхронизации «Читатели-Писатели» с приоритетом записи. Использованы стандартные средства языка C++ для работы с потоками и синхронизации: std::thread, std::mutex, std::atomic. Логирование действий каждого потока позволяет наглядно отслеживать порядок доступа к общему ресурсу и корректность работы алгоритма.
Программа успешно решает задачу управления доступом к общим данным в многопоточной среде и может быть использована как основа для более сложных систем с разделяемыми ресурсами.
Приложение
Полный текст программы:
#include <iostream>
#include <fstream>
#include <thread>
#include <locale>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <windows.h>
using namespace std;
FILE* streamOut = nullptr; // лог-файл
mutex writeMutex; // взаимная блокировка писателей
bool readLock = false;
mutex readLockMutex;
atomic<int> activeReaders(0);
void printTimeStamp() {
SYSTEMTIME t;
GetLocalTime(&t);
printf("%02d:%02d:%02d.%03d", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
if (streamOut) {
fprintf(streamOut, "%02d:%02d:%02d.%03d", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
}
}
void logPrint(const std::string& msg) {
static std::mutex logMutex;
std::lock_guard<std::mutex> lg(logMutex);
printTimeStamp();
printf("%s\n", msg.c_str());
if (streamOut) {
fprintf(streamOut, "%s\n", msg.c_str());
fflush(streamOut);
}
}
void writer(int totalWriters, atomic<int>& storage) {
int writerNumber;
vector<thread> writers;
for (writerNumber = 0; writerNumber < totalWriters; ++writerNumber) {
writers.emplace_back([writerNumber, &storage]() {
bool shown = false;
while (true) {
Sleep(rand() * (writerNumber + 1) % 15000 + 3000);
{
std::lock_guard<std::mutex> lg(writeMutex);
std::string msg = " Писатель " + std::to_string(writerNumber) + " обратился к хранилищу...";
logPrint(msg);
}
bool acquired = false;
shown = false;
while (!acquired) {
if (writeMutex.try_lock()) {
acquired = true;
} else {
if (!shown) {
std::string msg = "Другой писатель обратился к хранилищу раньше. Писатель " + std::to_string(writerNumber) + " ожидает.";
logPrint(msg);
shown = true;
}
Sleep(100);
}
}
{
std::lock_guard<std::mutex> rl(readLockMutex);
readLock = true;
}
shown = false;
while (activeReaders.load() != 0) {
if (!shown) {
std::string msg = "Писатель " + std::to_string(writerNumber) + " ожидает, пока активные читатели закончат работу с хранилищем.";
logPrint(msg);
shown = true;
}
Sleep(100);
}
printTimeStamp();
printf(": Писатель %d получил доступ к хранилищу.\n", writerNumber);
if (streamOut) fprintf(streamOut, ": Писатель %d получил доступ к хранилищу.\n", writerNumber);
Sleep(3000);
storage.store(rand() + writerNumber);
printTimeStamp();
printf(": Писатель %d записал в хранилище сообщение %d и закончил работу.\n", writerNumber, storage.load());
if (streamOut) fprintf(streamOut, ": Писатель %d записал в хранилище сообщение %d и закончил работу.\n", writerNumber, storage.load());
{
std::lock_guard<std::mutex> rl(readLockMutex);
readLock = false;
}
writeMutex.unlock();
}
});
}
for (auto& w : writers) w.join();
}
void reader(int totalReaders, atomic<int>& storage) {
vector<thread> readers;
for (int i = 0; i < totalReaders; ++i) {
readers.emplace_back([i, &storage]() {
bool reported = false;
while (true) {
Sleep(rand() * (i + 1) % 12000 + 3000);
{
std::lock_guard<std::mutex> lg(writeMutex);
printTimeStamp();
printf(": Читатель %d обратился к хранилищу...\n", i);
if (streamOut) fprintf(streamOut, ": Читатель %d обратился к хранилищу...\n", i);
}
while (true) {
bool locked;
{
std::lock_guard<std::mutex> rl(readLockMutex);
locked = readLock;
}
if (!locked) break;
if (!reported) {
std::string msg = "Доступ к хранилищу заблокирован. Читатель " + std::to_string(i) + " ожидает.";
logPrint(msg);
reported = true;
}
Sleep(100);
}
printTimeStamp();
printf(": Читатель %d получил доступ к хранилищу.\n", i);
if (streamOut) fprintf(streamOut, ": Читатель %d получил доступ к хранилищу.\n", i);
activeReaders.fetch_add(1);
Sleep(10000);
printTimeStamp();
printf(": Читатель %d прочитал из хранилища %d и закончил работу.\n", i, storage.load());
if (streamOut) fprintf(streamOut, ": Читатель %d прочитал из хранилища %d и закончил работу.\n", i, storage.load());
activeReaders.fetch_sub(1);
}
});
}
for (auto& r : readers) r.join();
}
int main() {
std::setlocale(LC_ALL, "Russian");
srand((unsigned)time(nullptr));
streamOut = fopen("log.txt", "w");
if (!streamOut) {
cerr << "Не удалось открыть log.txt" << endl;
return 1;
}
atomic<int> storage{0};
int totalWriters = 3;
int totalReaders = 5;
thread w(writer, totalWriters, std::ref(storage));
thread r(reader, totalReaders, std::ref(storage));
w.join();
r.join();
if (streamOut) fclose(streamOut);
return 0;
}
