1
0
This commit is contained in:
Sytnyk Yehor
2025-05-30 22:55:57 +03:00
parent 28ccfa72ac
commit e79109c81a
31 changed files with 1173 additions and 0 deletions

View File

@ -0,0 +1,7 @@
> [!NOTE]
> Викладач: Мельникова Р. В.
>
> Оцінка: in progress
> [!TIP]
> Виконано для Linux в команді.

View File

@ -0,0 +1,21 @@
title: Керування потоками одного та декількох процесів
subject: ОС
doctype: ЛБ
worknumber: 8
mentors:
- name: Мельнікова Р. В.,
gender: f,
degree: доц. каф. ПІ,
edu_program: &EDU ПЗПІ
university: ХНУРЕ
authors:
- name: Ситник Є. С.
course: 2
edu: *EDU
gender: m
group: 23-2
- name: Малишкін. А. С.
course: 2
edu: *EDU
gender: m
group: 23-2

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -0,0 +1,17 @@
CompileFlags:
Add: [-Wall, -Wextra, -std=c++23, -DBUILD_SHARED]
CompilationDatabase: build/
Diagnostics:
UnusedIncludes: Strict
InlayHints:
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
Index:
Background: Build
Hover:
ShowAKA: Yes

View File

@ -0,0 +1,73 @@
{
"files.associations": {
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"print": "cpp",
"queue": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"shared_mutex": "cpp"
}
}

View File

@ -0,0 +1,28 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++ build active file",
"command": "/usr/bin/g++",
"args": [
"-fdiagnostics-color=always",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}

View File

@ -0,0 +1,35 @@
CXX = clang++
CXXFLAGS = -std=c++23 -Wall -Wextra -O2
task-1/build/app: task-1/main.cpp task-1/queue.hpp task-1/workers.hpp
@$(CXX) $(CXXFLAGS) task-1/main.cpp -o task-1/build/app
task-1: task-1/build/app
@./task-1/build/app
task-2/build/app: task-2/main.cpp task-2/vector.hpp task-2/workers.hpp
@$(CXX) $(CXXFLAGS) task-2/main.cpp -o task-2/build/app
task-2: task-2/build/app
@./task-2/build/app
task-3/build/app: task-3/main.cpp task-3/forks.hpp task-3/workers.hpp
@$(CXX) $(CXXFLAGS) task-3/main.cpp -o task-3/build/app
task-3: task-3/build/app
@./task-3/build/app
launcher/build/app: launcher/main.cpp launcher/queue.hpp launcher/workers.hpp
@$(CXX) $(CXXFLAGS) launcher/main.cpp -o launcher/build/app
launcher/build/launcher: launcher/launcher.cpp
@$(CXX) $(CXXFLAGS) launcher/launcher.cpp -o launcher/build/launcher
launcher: launcher/build/app launcher/build/launcher
@./launcher/build/launcher
launcher-app: launcher/build/app
@./launcher/build/app
clean:
@find . -mindepth 2 -type d -name build -exec find {} -type f -delete \;

View File

@ -0,0 +1 @@
Hello from ./launcher/files/file1.txt

View File

@ -0,0 +1 @@
Hello from ./launcher/files/file2.txt

View File

@ -0,0 +1 @@
Hello from ./launcher/files/file3.txt

View File

@ -0,0 +1,23 @@
#include <chrono>
#include <cstdlib>
#include <print>
#include <ranges>
#include <thread>
int main() {
constexpr std::int32_t runs = 3;
constexpr auto interval = std::chrono::seconds();
for (std::int32_t i : std::views::iota(0, runs)) {
std::print("\nLaunching: ./launcher/build/app (run {})\n", i + 1);
std::int32_t result = std::system("./launcher/build/app");
if (result != 0) {
std::print("Run {} failed with code {}\n", i + 1, result);
}
std::this_thread::sleep_for(interval);
}
return 0;
}

View File

@ -0,0 +1,40 @@
#include "queue.hpp"
#include "workers.hpp"
#include <vector>
#include <string>
#include <fstream>
#include <ranges>
#include <format>
#include <thread>
int main() {
std::string prefix = "./launcher/files";
ipc::queue queue("/my_shared_queue");
std::vector<std::string> file_list = {
prefix + "/file1.txt",
prefix + "/file2.txt",
prefix + "/file3.txt"
};
for (auto&& name : file_list) {
std::ofstream(name) << std::format("Hello from {}\n", name);
}
std::vector<std::jthread> producers;
for (int _ : std::views::iota(0, 2)) {
producers.emplace_back(workers::producer, std::ref(queue), std::ref(file_list));
}
std::vector<std::jthread> consumers;
for (int _ : std::views::iota(0, 4)) {
consumers.emplace_back(workers::consumer, std::ref(queue));
}
producers.clear();
for (int _ : std::views::iota(0, 4)) {
queue.push("__DONE__");
}
}

View File

@ -0,0 +1,93 @@
#pragma once
#include <cstring>
#include <fcntl.h>
#include <semaphore.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
namespace ipc {
constexpr size_t MAX_PATH_LEN = 256;
constexpr size_t QUEUE_CAPACITY = 128;
struct shared_queue {
sem_t mutex;
sem_t slots;
sem_t items;
size_t head;
size_t tail;
char data[QUEUE_CAPACITY][MAX_PATH_LEN];
bool initialized;
};
class queue {
public:
queue(const char *shm_name) {
shm_fd_ = ::shm_open(shm_name, O_CREAT | O_RDWR, 0666);
if (shm_fd_ < 0)
throw std::runtime_error("shm_open failed");
if (::ftruncate(shm_fd_, sizeof(shared_queue)) < 0)
throw std::runtime_error("ftruncate failed");
ptr_ = static_cast<shared_queue *>(::mmap(nullptr, sizeof(shared_queue),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd_, 0));
if (ptr_ == MAP_FAILED)
throw std::runtime_error("mmap failed");
if (!ptr_->initialized) {
initialize();
ptr_->initialized = true;
}
}
~queue() {
::munmap(ptr_, sizeof(shared_queue));
::close(shm_fd_);
}
void push(const std::string &s) {
if (s.size() >= MAX_PATH_LEN)
throw std::length_error("path too long");
::sem_wait(&ptr_->slots);
::sem_wait(&ptr_->mutex);
std::strncpy(ptr_->data[ptr_->tail], s.c_str(), MAX_PATH_LEN);
ptr_->tail = (ptr_->tail + 1) % QUEUE_CAPACITY;
::sem_post(&ptr_->mutex);
::sem_post(&ptr_->items);
}
bool pop(std::string &out) {
::sem_wait(&ptr_->items);
::sem_wait(&ptr_->mutex);
char buf[MAX_PATH_LEN];
std::strncpy(buf, ptr_->data[ptr_->head], MAX_PATH_LEN);
ptr_->head = (ptr_->head + 1) % QUEUE_CAPACITY;
::sem_post(&ptr_->mutex);
::sem_post(&ptr_->slots);
out = buf;
return true;
}
private:
int shm_fd_;
shared_queue *ptr_;
void initialize() {
::sem_init(&ptr_->mutex, 1, 1);
::sem_init(&ptr_->slots, 1, QUEUE_CAPACITY);
::sem_init(&ptr_->items, 1, 0);
ptr_->head = ptr_->tail = 0;
}
};
} // namespace ipc

View File

@ -0,0 +1,34 @@
#pragma once
#include "queue.hpp"
#include <string>
#include <vector>
#include <thread>
#include <chrono>
#include <print>
#include <fstream>
namespace workers {
void producer(ipc::queue& q, const std::vector<std::string>& files) {
for (const auto& file : files) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
q.push(file);
std::print("Produced: {}\n", file);
}
}
void consumer(ipc::queue& queue) {
while (true) {
std::string path;
if (!queue.pop(path)) continue;
if (path == "__DONE__") break;
std::ifstream file(path);
std::print("Consumed: {}\n", path);
for (std::string line; std::getline(file, line);) {
std::print("\t{}\n", line);
}
}
}
}

View File

@ -0,0 +1 @@
Hello from ./task-1/files/file1.txt

View File

@ -0,0 +1 @@
Hello from ./task-1/files/file2.txt

View File

@ -0,0 +1 @@
Hello from ./task-1/files/file3.txt

View File

@ -0,0 +1,36 @@
#include "queue.hpp"
#include "workers.hpp"
#include <format>
#include <fstream>
#include <ranges>
#include <string>
#include <thread>
#include <vector>
int main() {
std::string prefix = "./task-1/files";
th_safe::queue<std::string> queue;
std::vector<std::string> file_list = {
prefix + "/file1.txt", prefix + "/file2.txt", prefix + "/file3.txt"};
for (auto &&name : file_list) {
std::ofstream(name) << std::format("Hello from {}\n", name);
}
std::vector<std::jthread> producers;
for (std::int32_t i : std::views::iota(0, 2)) {
producers.emplace_back(workers::producer, std::ref(queue),
std::ref(file_list), i);
}
std::vector<std::jthread> consumers;
for (std::int32_t i : std::views::iota(0, 4)) {
consumers.emplace_back(workers::consumer, std::ref(queue), i);
}
producers.clear();
queue.done();
}

View File

@ -0,0 +1,45 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <optional>
#include <queue>
namespace th_safe {
template <typename T> class queue {
public:
void push(const T &value) {
{
std::lock_guard lock(mutex_);
queue_.push(value);
}
cond_var_.notify_one();
}
std::optional<T> pop() {
std::unique_lock lock(mutex_);
cond_var_.wait(lock, [this] { return !queue_.empty() || done_; });
if (queue_.empty())
return std::nullopt;
T value = queue_.front();
queue_.pop();
return value;
}
void done() {
{
std::lock_guard lock(mutex_);
done_ = true;
}
cond_var_.notify_all();
}
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cond_var_;
bool done_ = false;
};
} // namespace th_safe

View File

@ -0,0 +1,40 @@
#pragma once
#include "queue.hpp"
#include <chrono>
#include <fstream>
#include <print>
#include <string>
#include <thread>
#include <vector>
namespace workers {
void producer(th_safe::queue<std::string> &q,
const std::vector<std::string> &files, int id) {
for (const auto &file : files) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
q.push(file);
std::print("Produced ({}): {}\n", id, file);
}
}
void consumer(th_safe::queue<std::string> &q, int id) {
while (true) {
auto item = q.pop();
if (!item.has_value())
break;
std::ifstream file(item.value());
if (!file) {
std::print("Failed to open: {}\n", item.value());
continue;
}
std::print("Consumed ({}): {}\n", id, item.value());
for (std::string line; std::getline(file, line);) {
std::print("\t{}\n", line);
}
}
}
} // namespace workers

View File

@ -0,0 +1,22 @@
#include "vector.hpp"
#include "workers.hpp"
#include <atomic>
#include <ranges>
#include <thread>
#include <vector>
int main() {
th_safe::vector<std::string> news;
std::atomic_bool done = false;
std::jthread writer_thread([&] {
workers::writer(news, 10);
done = true;
});
std::vector<std::jthread> readers;
for (std::int32_t id : std::views::iota(1, 4)) {
readers.emplace_back(workers::reader, std::cref(news), std::ref(done), id);
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <vector>
#include <mutex>
#include <shared_mutex>
#include <optional>
namespace th_safe {
template<typename T>
class vector {
public:
void push_back(const T& value) {
std::unique_lock lock(mutex_);
data_.push_back(value);
}
std::optional<T> back() const {
std::shared_lock lock(mutex_);
if (data_.empty()) return std::nullopt;
return data_.back();
}
std::size_t size() const {
std::shared_lock lock(mutex_);
return data_.size();
}
private:
mutable std::shared_mutex mutex_;
std::vector<T> data_;
};
}

View File

@ -0,0 +1,47 @@
#pragma once
#include "vector.hpp"
#include <chrono>
#include <print>
#include <ranges>
#include <string>
#include <thread>
namespace workers {
void writer(th_safe::vector<std::string> &news_vector,
std::int32_t count = 10) {
for (std::int32_t i : std::views::iota(0, count)) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
auto news = std::format("News item {}", i + 1);
news_vector.push_back(news);
std::print("Writer: added '{}'\n", news);
}
}
void reader(const th_safe::vector<std::string> &news_vector,
std::atomic_bool &done, std::int32_t id) {
std::size_t prev_size = 0;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
auto last = news_vector.back();
if (!last.has_value()) {
std::print("Reader {}: no news yet\n", id);
continue;
}
std::size_t current_size = news_vector.size();
if (current_size == prev_size && !done)
continue;
if (current_size == prev_size && done)
break;
std::print("Reader {}: reads '{}'\n", id, *last);
prev_size = current_size;
}
std::print("Reader {}: finished.\n", id);
}
} // namespace workers

View File

@ -0,0 +1,22 @@
#pragma once
#include <mutex>
#include <vector>
namespace th_safe {
class forks {
public:
explicit forks(std::size_t count) : mutexes_(count) {}
std::mutex &left(std::size_t i) { return mutexes_[i]; }
std::mutex &right(std::size_t i) {
return mutexes_[(i + 1) % mutexes_.size()];
}
std::size_t size() const { return mutexes_.size(); }
private:
std::vector<std::mutex> mutexes_;
};
} // namespace th_safe

View File

@ -0,0 +1,20 @@
#include "forks.hpp"
#include "workers.hpp"
#include <print>
#include <ranges>
#include <thread>
#include <vector>
int main() {
const std::size_t num_philosophers = 4;
const std::size_t rounds = 3;
th_safe::forks table(num_philosophers);
std::vector<std::jthread> philosophers;
for (auto id : std::views::iota(0UL, num_philosophers)) {
philosophers.emplace_back(workers::philosopher, id, std::ref(table),
rounds);
}
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "forks.hpp"
#include <algorithm>
#include <chrono>
#include <mutex>
#include <print>
#include <random>
#include <thread>
namespace workers {
void philosopher(std::size_t id, th_safe::forks &table,
std::size_t iterations = 3) {
std::mt19937 rng(id + std::random_device{}());
std::uniform_int_distribution<> think_time(100, 300);
std::uniform_int_distribution<> eat_time(100, 200);
for (std::size_t round = 0; round < iterations; ++round) {
std::print("Philosopher {} is thinking (round {})\n", id, round + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(think_time(rng)));
std::mutex &left_fork = table.left(id);
std::mutex &right_fork = table.right(id);
if (std::addressof(left_fork) < std::addressof(right_fork)) {
std::scoped_lock lock(left_fork, right_fork);
std::print("Philosopher {} is eating (round {})\n", id, round + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(eat_time(rng)));
std::print("Philosopher {} finished eating (round {})\n", id, round + 1);
} else {
std::scoped_lock lock(right_fork, left_fork);
std::print("Philosopher {} is eating (round {})\n", id, round + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(eat_time(rng)));
std::print("Philosopher {} finished eating (round {})\n", id, round + 1);
}
}
std::print("Philosopher {} leaves the table.\n", id);
}
} // namespace workers

View File

@ -0,0 +1,489 @@
#import "@local/nure:0.1.0": *
#show: pz-lb.with(..yaml("doc.yaml"))
#v(-spacing)
== Мета роботи
Метою даної лабораторної роботи є вивчення створення процесів та потоків при виконанні
програм.
== Хід роботи
Оскільки ми використовуємо операційну систему Linux, дану лабораторну роботу нами було виконано із використанням крос-платформних функцій стандартної бібліотеки C++, за виключенням останнього завдання, для якого було задіяно засоби POSIX API, адже стандартна бібліотека не надає засобів синхронізації процесів.
=== Задача №1
Мета даного завдання -- розробити програму, в якій декілька "виробників" (потоків) будуть знаходити імена файлів і додавати їх до спільної "черги". Одночасно декілька "споживачів" (інших потоків) братимуть ці імена файлів з черги і виводитимуть вміст кожного файлу.
Для перевірки роботи програми використаємо 2 потоки виробника та 4 потоки споживача, які будуть обробляти 3 файли.
```cpp
int main() {
std::string prefix = "./task-1/files";
th_safe::queue<std::string> queue;
std::vector<std::string> file_list = {
prefix + "/file1.txt",
prefix + "/file2.txt",
prefix + "/file3.txt"
};
for (auto &&name : file_list) {
std::ofstream(name) << std::format("Hello from {}\n", name);
}
std::vector<std::jthread> producers;
for (std::int32_t i : std::views::iota(0, 2)) {
producers.emplace_back(
workers::producer,
std::ref(queue),
std::ref(file_list),
i
);
}
std::vector<std::jthread> consumers;
for (std::int32_t i : std::views::iota(0, 4)) {
consumers.emplace_back(
workers::consumer,
std::ref(queue),
i
);
}
producers.clear();
queue.done();
}
```
Для зручнішої роботи із чергою з стандартної бібліотеки в багато-потоковому контексті створимо клас обгортку, що реалізує методи взаємодії із чергою з використанням м'ютексів.
```cpp
namespace th_safe {
template <typename T> class queue {
public:
void push(const T &value) {
{
std::lock_guard lock(mutex_);
queue_.push(value);
}
cond_var_.notify_one();
}
std::optional<T> pop() {
std::unique_lock lock(mutex_);
cond_var_.wait(
lock,
[this] { return !queue_.empty() || done_; }
);
if (queue_.empty())
return std::nullopt;
T value = queue_.front();
queue_.pop();
return value;
}
void done() {
{
std::lock_guard lock(mutex_);
done_ = true;
}
cond_var_.notify_all();
}
private:
std::queue<T> queue_;
std::mutex mutex_;
std::condition_variable cond_var_;
bool done_ = false;
};
} // namespace th_safe
```
Виробники додають файли в чергу, а споживачі зчитують їхній вміст.
```cpp
namespace workers {
void producer(
th_safe::queue<std::string> &q,
const std::vector<std::string> &files,
int id
) {
for (const auto &file : files) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
q.push(file);
std::print("Produced ({}): {}\n", id, file);
}
}
void consumer(th_safe::queue<std::string> &q, int id) {
while (true) {
auto item = q.pop();
if (!item.has_value())
break;
std::ifstream file(item.value());
if (!file) {
std::print("Failed to open: {}\n", item.value());
continue;
}
std::print("Consumed ({}): {}\n", id, item.value());
for (std::string line; std::getline(file, line);) {
std::print("\t{}\n", line);
}
}
}
} // namespace workers
```
Перевіримо правильність роботи програми
#figure(image("img/task-1-result.png", width: 70%), caption: [Результат виконання програми])
=== Задача №2
Мета даного завдання -- створити програму з потоками "читачів" та "письменників". Потоки-письменники будуть додавати нові записи в кінець спільного списку новин. Паралельно потоки-читачі будуть отримувати та читати останню додану новину з цього ж списку.
Для перевірки роботи програми використаємо 1 потік письменник та 3 потоки читачі.
```cpp
int main() {
th_safe::vector<std::string> news;
std::atomic_bool done = false;
std::jthread writer_thread([&] {
workers::writer(news, 10);
done = true;
});
std::vector<std::jthread> readers;
for (std::int32_t id : std::views::iota(1, 4)) {
readers.emplace_back(workers::reader, std::cref(news), std::ref(done), id);
}
}
```
Для зручнішої роботи із вектором з стандартної бібліотеки в багато-потоковому контексті створимо клас обгортку, що реалізує методи взаємодії із вектором з використанням м'ютексів.
```cpp
namespace th_safe {
template<typename T>
class vector {
public:
void push_back(const T& value) {
std::unique_lock lock(mutex_);
data_.push_back(value);
}
std::optional<T> back() const {
std::shared_lock lock(mutex_);
if (data_.empty()) return std::nullopt;
return data_.back();
}
std::size_t size() const {
std::shared_lock lock(mutex_);
return data_.size();
}
private:
mutable std::shared_mutex mutex_;
std::vector<T> data_;
};
}
```
Письменник додає новини в вектор, а читачі зчитують з вектора останню додану новину.
```cpp
namespace workers {
void writer(
th_safe::vector<std::string> &news_vector,
std::int32_t count = 10
) {
for (std::int32_t i : std::views::iota(0, count)) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
auto news = std::format("News item {}", i + 1);
news_vector.push_back(news);
std::print("Writer: added '{}'\n", news);
}
}
void reader(
const th_safe::vector<std::string> &news_vector,
std::atomic_bool &done,
std::int32_t id
) {
std::size_t prev_size = 0;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
auto last = news_vector.back();
if (!last.has_value()) {
std::print("Reader {}: no news yet\n", id);
continue;
}
std::size_t current_size = news_vector.size();
if (current_size == prev_size && !done)
continue;
if (current_size == prev_size && done)
break;
std::print("Reader {}: reads '{}'\n", id, *last);
prev_size = current_size;
}
std::print("Reader {}: finished.\n", id);
}
} // namespace workers
```
Перевіримо правильність роботи програми
#figure(image("img/task-2-result.png"), caption: [Результат виконання програми])
=== Задача №3
Мета даного завдання -- реалізувати програму, яка моделює відому проблему "обідаючих філософів" за допомогою потоків. Кожен потік представлятиме філософа, який проходить повний цикл дій: думає, бере по черзі дві виделки, обідає, а потім кладе виделки на місце. Цей цикл має повторюватися задану кількість разів для кожного філософа.
Для перевірки роботи програми використаємо 5 потоків філософів та 3 повторення.
```cpp
int main() {
const std::size_t num_philosophers = 5;
const std::size_t rounds = 3;
th_safe::forks table(num_philosophers);
std::vector<std::jthread> philosophers;
for (auto id : std::views::iota(0UL, num_philosophers)) {
philosophers.emplace_back(
workers::philosopher,
id,
std::ref(table),
rounds
);
}
}
```
Для зручної роботи із "столом" в багато-потоковому контексті створимо клас, що реалізує методи взаємодії із столом з використанням м'ютексів.
```cpp
namespace th_safe {
class forks {
public:
explicit forks(std::size_t count) : mutexes_(count) {}
std::mutex &left(std::size_t i) { return mutexes_[i]; }
std::mutex &right(std::size_t i) {
return mutexes_[(i + 1) % mutexes_.size()];
}
std::size_t size() const { return mutexes_.size(); }
private:
std::vector<std::mutex> mutexes_;
};
} // namespace th_safe
```
Кожен з філософів по черзі із певною затримкою виконує одну із можливих дій.
```cpp
namespace workers {
void philosopher(
std::size_t id,
th_safe::forks &table,
std::size_t iterations = 3
) {
std::mt19937 rng(id + std::random_device{}());
std::uniform_int_distribution<> think_time(100, 300);
std::uniform_int_distribution<> eat_time(100, 200);
for (std::size_t round = 0; round < iterations; ++round) {
std::print(
"Philosopher {} is thinking (round {})\n",
id,
round + 1
);
std::this_thread::sleep_for(
std::chrono::milliseconds(think_time(rng))
);
std::mutex &left_fork = table.left(id);
std::mutex &right_fork = table.right(id);
if (std::addressof(left_fork) < std::addressof(right_fork)) {
std::scoped_lock lock(left_fork, right_fork);
std::print(
"Philosopher {} is eating (round {})\n",
id,
round + 1
);
std::this_thread::sleep_for(
std::chrono::milliseconds(eat_time(rng))
);
std::print(
"Philosopher {} finished eating (round {})\n",
id,
round + 1
);
} else {
std::scoped_lock lock(right_fork, left_fork);
std::print("Philosopher {
} is eating (round {})\n", id, round + 1);
std::this_thread::sleep_for(
std::chrono::milliseconds(eat_time(rng))
);
std::print(
"Philosopher {} finished eating (round {})\n",
id,
round + 1
);
}
}
std::print("Philosopher {} leaves the table.\n", id);
}
} // namespace workers
```
Перевіримо правильність роботи програми
#figure(image("img/task-3-result.png", height: 80%), caption: [Результат виконання програми])
=== Завдання на найвищу оцінку
Мета даного завдання -- створити окрему програму, яка буде багаторазово запускати одну з попередніх програм. Ці запуски повинні відбуватися за певним заданим розкладом, використовуючи механізм, такий як WaitableTimer.
Виконаємо завдання для програми №1.
Перевіримо роботу із 3 окремим процесами.
```cpp
int main() {
constexpr std::int32_t runs = 3;
constexpr auto interval = std::chrono::seconds();
for (std::int32_t i : std::views::iota(0, runs)) {
std::print("\nLaunching: ./launcher/build/app (run {})\n", i + 1);
std::int32_t result = std::system("./launcher/build/app");
if (result != 0) {
std::print("Run {} failed with code {}\n", i + 1, result);
}
std::this_thread::sleep_for(interval);
}
return 0;
}
```
Щоб кілька окремих процесів мали доступ до однієї черги використаємо функцію "mmap". Для синхронізації кількох процесів скористаємося семафорами.
```cpp
namespace ipc {
constexpr size_t MAX_PATH_LEN = 256;
constexpr size_t QUEUE_CAPACITY = 128;
struct shared_queue {
sem_t mutex;
sem_t slots;
sem_t items;
size_t head;
size_t tail;
char data[QUEUE_CAPACITY][MAX_PATH_LEN];
bool initialized;
};
class queue {
public:
queue(const char *shm_name) {
shm_fd_ = ::shm_open(shm_name, O_CREAT | O_RDWR, 0666);
if (shm_fd_ < 0)
throw std::runtime_error("shm_open failed");
if (::ftruncate(shm_fd_, sizeof(shared_queue)) < 0)
throw std::runtime_error("ftruncate failed");
ptr_ = static_cast<shared_queue *>(
::mmap(
nullptr,
sizeof(shared_queue),
PROT_READ | PROT_WRITE,
MAP_SHARED,
shm_fd_,
0
)
);
if (ptr_ == MAP_FAILED)
throw std::runtime_error("mmap failed");
if (!ptr_->initialized) {
initialize();
ptr_->initialized = true;
}
}
~queue() {
::munmap(ptr_, sizeof(shared_queue));
::close(shm_fd_);
}
void push(const std::string &s) {
if (s.size() >= MAX_PATH_LEN)
throw std::length_error("path too long");
::sem_wait(&ptr_->slots);
::sem_wait(&ptr_->mutex);
std::strncpy(ptr_->data[ptr_->tail], s.c_str(), MAX_PATH_LEN);
ptr_->tail = (ptr_->tail + 1) % QUEUE_CAPACITY;
::sem_post(&ptr_->mutex);
::sem_post(&ptr_->items);
}
bool pop(std::string &out) {
::sem_wait(&ptr_->items);
::sem_wait(&ptr_->mutex);
char buf[MAX_PATH_LEN];
std::strncpy(buf, ptr_->data[ptr_->head], MAX_PATH_LEN);
ptr_->head = (ptr_->head + 1) % QUEUE_CAPACITY;
::sem_post(&ptr_->mutex);
::sem_post(&ptr_->slots);
out = buf;
return true;
}
private:
int shm_fd_;
shared_queue *ptr_;
void initialize() {
::sem_init(&ptr_->mutex, 1, 1);
::sem_init(&ptr_->slots, 1, QUEUE_CAPACITY);
::sem_init(&ptr_->items, 1, 0);
ptr_->head = ptr_->tail = 0;
}
};
} // namespace ipc
```
Перевіримо роботу зміненої програми.
#figure(image("img/task-4-result.png", height: 72%), caption: [Результат виконання програми])
== Висновки
Під час даної лабораторної роботи ми навчились використовувати процеси та потоки при виконанні програм.
#show: appendices_style