
Готовые отчеты / ОСиС. Лабораторная работа 6
.pdfФедеральное агентство связи ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ
ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ. ПРОФ. М. А. БОНЧ-БРУЕВИЧА» (СПбГУТ)
Факультет инфокоммуникационных сетей и систем Кафедра программной инженерии и вычислительной техники
ЛАБОРАТОРНАЯ РАБОТА №6 по дисциплине «Операционные системы и сети»
на тему «Разработка многопоточной программы под Windows»
Выполнил: студент 3-го курса дневного отделения группы ИКПИ-85
Коваленко Леонид Александрович Преподаватель:
доцент кафедры ПИиВТ Дагаев Александр Владимирович
Санкт-Петербург 2020

Цель работы Разработать многопоточную программу с применением механизмов
синхронизации потоков под Windows. Постановка задачи
1.Написать скрипт для отображения списка запущенных процессов, поиска по названию процессов и запуска диспетчера задач и продемонстрировать его работу.
2.Написать программу на C++ для решения задачи об обедающих философах и продемонстрировать ее работу.
Ход работы
Работа выполняется в операционной системе MS Windows 10 Pro. Напишем скрипт для отображения списка запущенных процессов,
поиска по названию процессов и запуска монитора процессов (табл. 1). Таблица 1 — Файл main.ps1
while ($true) {
echo "-== Меню ==-"
echo "1. Список всех процессов." echo "2. Поиск по всем процессам." echo "3. Диспетчер задач."
$n = (Read-Host "Ввод") if ($n -LIKE "1*") {
Get-Process
}
elseif ($n -LIKE "2*") {
$substr = (Read-Host "Введите название")
$is_all = (Read-Host "Остановить все найденные процессы? (y/n)") if ($is_all -LIKE "y*") {
Get-Process -Name "$substr" | Stop-Process
}
else {
$is_all = (Read-Host "Получить подробную информацию по всем найде нным процессам? (y/n)")
if ($is_all -LIKE "y*") {
Get-Process -Name "$substr" | Format-List *
}
}
}
elseif ($n -LIKE "3*") { taskmgr
}
elseif ($n -LIKE "exit*") { exit
}
else {
echo "Ошибка ввода"
}
}
Запустим скрипт на выполнение и проверим его работу (рис. 1, 2, 3, 4).
2

Рисунок 1 — Проверка первого пункта меню
Рисунок 2 — Проверка второго пункта меню
Рисунок 3 — Проверка второго пункта меню (2) 3

Рисунок 4 — Проверка третьего пункта меню
Для того, чтобы выйти, достаточно ввести exit (и нажать Enter) или нажать Ctrl+C.
Если мы попытаемся скомпилировать код программы для решения задачи об обедающих философах из лабораторной работы №5 (Linux; язык C++) в Windows, то получим следующее сообщение об ошибке (рис. 5).
Рисунок 5 — Сообщение об ошибке компиляции Информации из Интернета достаточно, чтобы прийти к выводу об
отсутствии поддержки некоторых возможностей C++11 (в нашем случае — классов thread и mutex) компилятором GCC-10.2.0-MinGW. Есть два варианта: воспользоваться компилятором MinGW-w64 или не менять компилятор и разработать свои классы thread и mutex (классы-обертки над функциями WinAPI). Попробуем второй вариант.
Напишем программу на C++ для решения задачи об обедающих философах (табл. 2, 3).
4

Таблица 2 — Файл invoke.h1 (источник: meganz/mingw-std-threads)
#ifndef MINGW_INVOKE_H_ #define MINGW_INVOKE_H_
#include <type_traits> // For std::result_of, etc. #include <utility> // For std::forward
#include <functional> // For std::reference_wrapper
namespace mingw_stdthread { namespace detail {
// For compatibility, implement std::invoke for C++11 and C++14 #if __cplusplus < 201703L
template<bool PMemFunc, bool PMemData> struct Invoker {
template<class F, class... Args>
inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args) {
return std::forward<F>(f)(std::forward<Args>(args)...);
}
}; template<bool>
struct InvokerHelper;
template<>
struct InvokerHelper<false> { template<class T1>
inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1)) { return *std::forward<T1>(t1);
}
template<class T1>
inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get()) {
return t1.get();
}
};
template<>
struct InvokerHelper<true> { template<class T1>
inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1)) { return std::forward<T1>(t1);
}
};
template<>
struct Invoker<true, false> {
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ decltype((InvokerHelper<std::is_base_of<T,typename
std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f) (std::forward<Args>(args)...)) {
return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f) (std::forward<Args>(args)...);
}
};
template<>
struct Invoker<false, true> {
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
1В нашем случае этот файл нужен для получения возможности задания переменного числа аргументов (параметров функции потока) в конструкторе Thread
5

decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f) {
return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
}
};
template<class F, class... Args> struct InvokeResult {
typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
(sizeof...(Args) == 1)> invoker;
inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...)) {
return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
};
template<class F, class...Args>
auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...)) {
return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
#else
using std::invoke; #endif
}// Namespace "detail"
}// Namespace "mingw_stdthread"
#endif
Таблица 3 — Файл main.cpp
#include "invoke.h" #include <iostream> #include <functional> #include <stdexcept> #include <Windows.h>
// Класс-обертка "Поток" 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);
}
6

}
// Отделение потока выполнения от объекта потока 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() {
7

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;
};
8

// Класс "Философ" 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() {
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;
{
9

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;
}
Алгоритм решения аналогичен алгоритму решения на основе монитора2. (Недостатком является отсутствие решения проблемы голодания: один из философов (или несколько) может долго ждать своих соседей.)
Запустим программу на выполнение и проверим её работу (рис. 6).
Рисунок 6 — Работа алгоритма решения задачи об обедающих философах В данном случае мы использовали C++, а не Си, так как задача лежит
ближе к ООП, нежели к чисто процедурной парадигме.
2https://ru.wikipedia.org/wiki/Задача_об_обедающих_философах#Решение_на_основе_монитора
10