Министерство цифрового развития, связи и массовых коммуникаций Российской Федерации
Федеральное государственное бюджетное образовательное учреждение Высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. Проф. М. А. Бонч-Бруевича» (СПбГУТ)
Факультет Информационных технологий и программной инженерии
Кафедра Программной инженерии
Лабораторная работа №4
По дисциплине: Операционные системы и сети
Выполнил студент:
Яковлев М. А. ИКПИ-32
Принял работу:
Дагаев А. В.
Дата выполнения:
«15» ноября 2025 г.
Санкт-Петербург
2025 г.
Постановка задачи
Необходимо реализовать модель проблемы обедающих философов, которая иллюстрирует концепцию синхронизации процессов и взаимодействия между ними. Задача заключается в симуляции поведения философов, сидящих за столом, где каждый философ может либо думать, либо есть. Для того чтобы поесть, философу необходимо взять две вилки, расположенные по обе стороны от него. При этом важно обеспечить, чтобы не возникало взаимных блокировок, когда несколько философов пытаются одновременно взять вилки.
Перечень функций
Класс Thread (Поток)
Thread(_Callable && callable, _Args && ...args) Конструктор, создаёт поток с функцией callable и аргументами args, запускает новый поток через CreateThread.
~Thread() Деструктор, завершающий поток, если он ещё активен (через TerminateThread и закрывает дескриптор).
void detach() Отделяет поток от объекта, закрывая дескриптор (поток продолжает работать, объект не может с ним взаимодействовать).
void join() Ожидание завершения потока и закрытие дескриптора.
bool joinable() Проверяет, является ли поток активным (дескриптор не равен INVALID_HANDLE).
template<class T> static DWORD WINAPI run(void * threadArgument) Вспомогательная статическая функция для запуска потоковой функции, вызывая переданный callable.
Класс Mutex (Мьютекс)
Mutex() Конструктор, создаёт объект мьютекса через CreateMutexA.
~Mutex() Деструктор, закрывает дескриптор мьютекса.
void lock() Захватывает мьютекс с ожиданием (блокирует).
bool tryLock() Пытается захватить мьютекс без ожидания, возвращает true, если удалось.
void unlock() Освобождает мьютекс.
Класс Fork (Вилка)
Fork(std::string name) Конструктор с именем вилки.
void take() Захватывает вилку (фиксирует мьютекс).
bool tryTake() Пытается захватить вилку без ожидания.
void put() Освобождает вилку (разблокирует мьютекс).
std::string getName() const Возвращает имя вилки.
Класс Philosopher (Философ)
Philosopher(const std::string &name, Fork &leftFork, Fork &rightFork) Конструктор, инициализирует философа с именем и двумя вилками (левой и правой).
void run(int iteration) Основная логика философа: в цикле пытается взять вилки, ест (симулируется выводом и ожиданием), потом кладёт вилки и повторяет заданное число итераций.
void wait(int milliseconds = 100) const Функция ожидания (Sleep) для симуляции задержек.
Функция main
Настраивает локаль, создаёт вилки и философов, запускает их выполнение в отдельных потоках и ждёт завершения потоков.
Показывает обед двух философов, затем обед пяти философов, выводя соответствующие сообщения на консоль.
Описание алгоритма
Алгоритм задачи обедающих философов моделирует ситуацию, где несколько философов сидят за круглым столом, каждый с вилкой слева и справа от себя. Философы чередуют две активности — размышления и еду. Чтобы поесть, философ должен одновременно взять обе вилки — левую и правую. Проблема состоит в том, что вилок меньше, чем философов, и если каждый философ одновременно возьмёт вилку слева и будет ожидать вилки справа, возможна взаимная блокировка (deadlock), когда никто не сможет продолжить.
Для предотвращения deadlock в данной реализации используется механизм синхронизации — мьютексы (Mutex), которые защищают доступ к вилкам. Каждый объект "Вилка" содержит мьютекс, который захватывается при взятии вилки и освобождается при её возвращении на стол. Философ пытается захватить левую вилку целиком (блокировка мьютекса). Затем он пытается без ожидания (tryLock) захватить правую вилку. Если правая вилка занята, философ освобождает левую вилку и начинает цикл заново, тем самым не блокируя систему.
Алгоритм работы философа состоит из цикла: связь с размышлениями, попытка захватить обе вилки двумя вызовами lock/tryLock, затем обед с выводом сообщений и паузами (симуляция времени еды). После еды вилки освобождаются, и философ продолжает цикл, уменьшая счётчик итераций.
Потоки реализованы через создание класса Thread, который оборачивает Windows API CreateThread, запускает функцию run философа и предоставляет методы join и detach для синхронизации с потоком. Мьютексы и вилки гарантируют, что одновременно каждый ресурс может быть занят только одним философом, что предотвращает гонки и обеспечивает корректный доступ.
Программа на C++
main.cpp
#include "invoke.h"
#include <iostream>
#include <functional>
#include <stdexcept>
#include <Windows.h>
#include <locale>
// Класс-обертка "Поток"
class Thread
{
public:
// Конструктор
template < typename _Callable, typename..._Args >
Thread(_Callable && callable, _Args && ...args)
{
mThreadArgument = [&callable, &args...] ()
{
mingw_stdthread::detail::invoke(callable, args...);
};
mHandle = CreateThread(NULL, 0, run<decltype(mThreadArgument)>,
&mThreadArgument, 0, NULL);
if (mHandle == 0)
{
throw std::system_error(GetLastError(), std::generic_category());
}
}
// Деструктор
~Thread()
{
if (this->joinable())
{
TerminateThread(mHandle, 0);
CloseHandle(mHandle);
}
}
// Отделение потока выполнения от объекта потока
void detach()
{
if (this->joinable())
{
if (!CloseHandle(mHandle))
{
throw std::system_error(GetLastError(),
std::generic_category());
}
mHandle = INVALID_HANDLE;
}
else throw std::invalid_argument("joinable() == false!");
}
// Блокировка текущего потока до тех пор, пока поток, обозначенный *this,
void join()
{
if (this->joinable())
{
if (WaitForSingleObject(mHandle, INFINITE) != WAIT_OBJECT_0)
{
throw std::system_error(GetLastError(),
std::generic_category());
}
mHandle = INVALID_HANDLE;
}
else throw std::invalid_argument("joinable() == false!");
}
// Проверка, идентифицирует ли объект Thread активный поток выполнения
bool joinable()
{
return mHandle != INVALID_HANDLE;
}
private:
// Идентификатор дескриптора потока
HANDLE mHandle;
// [Константа] Неверный идентификатор дескриптора потока
static constexpr HANDLE INVALID_HANDLE = nullptr;
// Функция-обертка для аргумента потока
std::function<void()> mThreadArgument;
// Функция потока
template < class T >
static DWORD WINAPI run(void * threadArgument)
{
(*(T*) threadArgument)();
return 0;
}
// Конструктор копирования запрещен
Thread(const Thread & ) = delete;
// Оператор копирования запрещен
Thread & operator = (const Thread & ) = delete;
};
// Класс-обертка "Мьютекс"
class Mutex
{
public:
// Конструктор
Mutex()
{
mHandle = CreateMutexA(NULL, FALSE, NULL);
if (mHandle == 0)
{
throw std::system_error(GetLastError(), std::generic_category());
}
}
// Деструктор
~Mutex()
{
CloseHandle(mHandle);
}
// Захват мьютекса с ожиданием
void lock()
{
if (WaitForSingleObject(mHandle, INFINITE) != WAIT_OBJECT_0)
{
throw std::system_error(GetLastError(), std::generic_category());
}
}
// Попытка захвата мьютекса без ожидания
bool tryLock()
{
return WaitForSingleObject(mHandle, 0) == WAIT_OBJECT_0;
}
// Освобождение мьютекса
void unlock()
{
if (!ReleaseMutex(mHandle))
{
throw std::system_error(GetLastError(), std::generic_category());
}
}
private:
// Идентификатор дескриптора мьютекса
HANDLE mHandle;
// Конструктор копирования запрещен
Mutex(const Mutex & ) = delete;
// Оператор копирования запрещен
Mutex & operator = (const Mutex & ) = delete;
};
// Класс "Вилка"
class Fork
{
public:
// Конструктор
Fork(std::string name) : mName(name) {}
// Ожидание и взятие вилки
void take()
{
mMutex.lock();
}
// Попытка взятия вилки
bool tryTake()
{
return mMutex.tryLock();
}
// Освобождение вилки
void put()
{
mMutex.unlock();
}
// Получение условного названия вилки
std::string getName() const
{
return mName;
}
private:
// Мьютекс
Mutex mMutex;
// Условное название вилки
std::string mName;
};
// Класс "Философ"
class Philosopher
{
public:
// Конструктор
Philosopher(const std::string &name, Fork &leftFork, Fork &rightFork)
: mName(name), mLeftFork(&leftFork), mRightFork(&rightFork) {}
// Функция потока
void run(int iteration)
{
std::string in = mName + " философ начал трапезу\n";
std::string out = mName + " философ приостановил трапезу\n";
while (iteration > 0)
{
// Думаем
wait();
// Берем две вилки или блокируемся
mLeftFork->take();
if (!mRightFork->tryTake())
{
mLeftFork->put();
continue;
}
// Обедаем
std::cout << in;
std::cout.flush();
wait();
std::cout << out;
std::cout.flush();
// Кладем на стол обе вилки
mLeftFork->put();
wait();
mRightFork->put();
wait();
--iteration;
}
}
private:
// Ожидание milliseconds миллисекунд
void wait(int milliseconds = 100) const
{
Sleep(milliseconds);
}
// Имя философа
std::string mName;
// Левая и правая вилки
Fork *mLeftFork, *mRightFork;
};
int main()
{
setlocale(LC_ALL, "Russian");
using namespace std;
std::cout << "[Старт] Начался обед двух философов" << std::endl;
{
Fork f1("#1"), f2("#2");
Philosopher p1("#1", f1, f2), p2("#2", f2, f1);
Thread thread1(&Philosopher::run, &p1, 2);
Thread thread2(&Philosopher::run, &p2, 2);
thread1.join();
thread2.join();
}
std::cout << "[Финиш] Два философа пообедали" << std::endl;
std::cout << "[Старт] Начался обед пяти философов" << std::endl;
{
Fork f1("#1"), f2("#2"), f3("#3"), f4("#4"), f5("#5");
Philosopher p1("#1", f1, f2), p2("#2", f2, f3), p3("#3", f3, f4),
p4("#4", f4, f5), p5("#5", f5, f1);
Thread thread1(&Philosopher::run, &p1, 2);
Thread thread2(&Philosopher::run, &p2, 2);
Thread thread3(&Philosopher::run, &p3, 2);
Thread thread4(&Philosopher::run, &p4, 2);
Thread thread5(&Philosopher::run, &p5, 2);
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
}
std::cout << "[Финиш] Пять философов пообедали" << std::endl;
return 0;
}
