diff --git a/semester-4/ОС/lb-8/README.md b/semester-4/ОС/lb-8/README.md new file mode 100644 index 0000000..db06797 --- /dev/null +++ b/semester-4/ОС/lb-8/README.md @@ -0,0 +1,7 @@ +> [!NOTE] +> Викладач: Мельникова Р. В. +> +> Оцінка: in progress + +> [!TIP] +> Виконано для Linux в команді. diff --git a/semester-4/ОС/lb-8/doc.yaml b/semester-4/ОС/lb-8/doc.yaml new file mode 100644 index 0000000..89520fd --- /dev/null +++ b/semester-4/ОС/lb-8/doc.yaml @@ -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 diff --git a/semester-4/ОС/lb-8/img/task-1-result.png b/semester-4/ОС/lb-8/img/task-1-result.png new file mode 100644 index 0000000..d9fd6c0 Binary files /dev/null and b/semester-4/ОС/lb-8/img/task-1-result.png differ diff --git a/semester-4/ОС/lb-8/img/task-2-result.png b/semester-4/ОС/lb-8/img/task-2-result.png new file mode 100644 index 0000000..65ed772 Binary files /dev/null and b/semester-4/ОС/lb-8/img/task-2-result.png differ diff --git a/semester-4/ОС/lb-8/img/task-3-result.png b/semester-4/ОС/lb-8/img/task-3-result.png new file mode 100644 index 0000000..1d67c0b Binary files /dev/null and b/semester-4/ОС/lb-8/img/task-3-result.png differ diff --git a/semester-4/ОС/lb-8/img/task-4-result.png b/semester-4/ОС/lb-8/img/task-4-result.png new file mode 100644 index 0000000..4316e91 Binary files /dev/null and b/semester-4/ОС/lb-8/img/task-4-result.png differ diff --git a/semester-4/ОС/lb-8/src/.clangd b/semester-4/ОС/lb-8/src/.clangd new file mode 100644 index 0000000..5273bda --- /dev/null +++ b/semester-4/ОС/lb-8/src/.clangd @@ -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 diff --git a/semester-4/ОС/lb-8/src/.vscode/settings.json b/semester-4/ОС/lb-8/src/.vscode/settings.json new file mode 100644 index 0000000..7fd9674 --- /dev/null +++ b/semester-4/ОС/lb-8/src/.vscode/settings.json @@ -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" + } +} \ No newline at end of file diff --git a/semester-4/ОС/lb-8/src/.vscode/tasks.json b/semester-4/ОС/lb-8/src/.vscode/tasks.json new file mode 100644 index 0000000..05054c5 --- /dev/null +++ b/semester-4/ОС/lb-8/src/.vscode/tasks.json @@ -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" +} \ No newline at end of file diff --git a/semester-4/ОС/lb-8/src/Makefile b/semester-4/ОС/lb-8/src/Makefile new file mode 100644 index 0000000..9b7f28f --- /dev/null +++ b/semester-4/ОС/lb-8/src/Makefile @@ -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 \; diff --git a/semester-4/ОС/lb-8/src/launcher/files/file1.txt b/semester-4/ОС/lb-8/src/launcher/files/file1.txt new file mode 100644 index 0000000..7d67a04 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/files/file1.txt @@ -0,0 +1 @@ +Hello from ./launcher/files/file1.txt diff --git a/semester-4/ОС/lb-8/src/launcher/files/file2.txt b/semester-4/ОС/lb-8/src/launcher/files/file2.txt new file mode 100644 index 0000000..74d5526 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/files/file2.txt @@ -0,0 +1 @@ +Hello from ./launcher/files/file2.txt diff --git a/semester-4/ОС/lb-8/src/launcher/files/file3.txt b/semester-4/ОС/lb-8/src/launcher/files/file3.txt new file mode 100644 index 0000000..d33d879 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/files/file3.txt @@ -0,0 +1 @@ +Hello from ./launcher/files/file3.txt diff --git a/semester-4/ОС/lb-8/src/launcher/launcher.cpp b/semester-4/ОС/lb-8/src/launcher/launcher.cpp new file mode 100644 index 0000000..49e335f --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/launcher.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +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; +} diff --git a/semester-4/ОС/lb-8/src/launcher/main.cpp b/semester-4/ОС/lb-8/src/launcher/main.cpp new file mode 100644 index 0000000..b53fd85 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/main.cpp @@ -0,0 +1,40 @@ +#include "queue.hpp" +#include "workers.hpp" + +#include +#include +#include +#include +#include +#include + +int main() { + std::string prefix = "./launcher/files"; + ipc::queue queue("/my_shared_queue"); + + std::vector 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 producers; + for (int _ : std::views::iota(0, 2)) { + producers.emplace_back(workers::producer, std::ref(queue), std::ref(file_list)); + } + + std::vector 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__"); + } +} diff --git a/semester-4/ОС/lb-8/src/launcher/queue.hpp b/semester-4/ОС/lb-8/src/launcher/queue.hpp new file mode 100644 index 0000000..51d00d3 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/queue.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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(::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 diff --git a/semester-4/ОС/lb-8/src/launcher/workers.hpp b/semester-4/ОС/lb-8/src/launcher/workers.hpp new file mode 100644 index 0000000..e242de6 --- /dev/null +++ b/semester-4/ОС/lb-8/src/launcher/workers.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "queue.hpp" +#include +#include +#include +#include +#include +#include + +namespace workers { + void producer(ipc::queue& q, const std::vector& 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); + } + } + } +} \ No newline at end of file diff --git a/semester-4/ОС/lb-8/src/task-1/files/file1.txt b/semester-4/ОС/lb-8/src/task-1/files/file1.txt new file mode 100644 index 0000000..df71d67 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/files/file1.txt @@ -0,0 +1 @@ +Hello from ./task-1/files/file1.txt diff --git a/semester-4/ОС/lb-8/src/task-1/files/file2.txt b/semester-4/ОС/lb-8/src/task-1/files/file2.txt new file mode 100644 index 0000000..32f5beb --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/files/file2.txt @@ -0,0 +1 @@ +Hello from ./task-1/files/file2.txt diff --git a/semester-4/ОС/lb-8/src/task-1/files/file3.txt b/semester-4/ОС/lb-8/src/task-1/files/file3.txt new file mode 100644 index 0000000..e1ee558 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/files/file3.txt @@ -0,0 +1 @@ +Hello from ./task-1/files/file3.txt diff --git a/semester-4/ОС/lb-8/src/task-1/main.cpp b/semester-4/ОС/lb-8/src/task-1/main.cpp new file mode 100644 index 0000000..c0e76ef --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/main.cpp @@ -0,0 +1,36 @@ +#include "queue.hpp" +#include "workers.hpp" + +#include +#include +#include +#include +#include +#include + +int main() { + std::string prefix = "./task-1/files"; + + th_safe::queue queue; + + std::vector 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 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 consumers; + for (std::int32_t i : std::views::iota(0, 4)) { + consumers.emplace_back(workers::consumer, std::ref(queue), i); + } + + producers.clear(); + queue.done(); +} diff --git a/semester-4/ОС/lb-8/src/task-1/queue.hpp b/semester-4/ОС/lb-8/src/task-1/queue.hpp new file mode 100644 index 0000000..1d9862d --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/queue.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +namespace th_safe { +template class queue { +public: + void push(const T &value) { + { + std::lock_guard lock(mutex_); + queue_.push(value); + } + cond_var_.notify_one(); + } + + std::optional 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 queue_; + std::mutex mutex_; + std::condition_variable cond_var_; + bool done_ = false; +}; +} // namespace th_safe diff --git a/semester-4/ОС/lb-8/src/task-1/workers.hpp b/semester-4/ОС/lb-8/src/task-1/workers.hpp new file mode 100644 index 0000000..a2138e0 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-1/workers.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "queue.hpp" +#include +#include +#include +#include +#include +#include + +namespace workers { +void producer(th_safe::queue &q, + const std::vector &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 &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 diff --git a/semester-4/ОС/lb-8/src/task-2/main.cpp b/semester-4/ОС/lb-8/src/task-2/main.cpp new file mode 100644 index 0000000..e19e8a0 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-2/main.cpp @@ -0,0 +1,22 @@ +#include "vector.hpp" +#include "workers.hpp" + +#include +#include +#include +#include + +int main() { + th_safe::vector news; + std::atomic_bool done = false; + + std::jthread writer_thread([&] { + workers::writer(news, 10); + done = true; + }); + + std::vector readers; + for (std::int32_t id : std::views::iota(1, 4)) { + readers.emplace_back(workers::reader, std::cref(news), std::ref(done), id); + } +} diff --git a/semester-4/ОС/lb-8/src/task-2/vector.hpp b/semester-4/ОС/lb-8/src/task-2/vector.hpp new file mode 100644 index 0000000..115d2f8 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-2/vector.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace th_safe { + template + class vector { + public: + void push_back(const T& value) { + std::unique_lock lock(mutex_); + data_.push_back(value); + } + + std::optional 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 data_; + }; +} \ No newline at end of file diff --git a/semester-4/ОС/lb-8/src/task-2/workers.hpp b/semester-4/ОС/lb-8/src/task-2/workers.hpp new file mode 100644 index 0000000..dd66e34 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-2/workers.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "vector.hpp" +#include +#include +#include +#include +#include + +namespace workers { +void writer(th_safe::vector &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 &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 diff --git a/semester-4/ОС/lb-8/src/task-3/forks.hpp b/semester-4/ОС/lb-8/src/task-3/forks.hpp new file mode 100644 index 0000000..a1f4000 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-3/forks.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +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 mutexes_; +}; +} // namespace th_safe diff --git a/semester-4/ОС/lb-8/src/task-3/main.cpp b/semester-4/ОС/lb-8/src/task-3/main.cpp new file mode 100644 index 0000000..f8ea84c --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-3/main.cpp @@ -0,0 +1,20 @@ +#include "forks.hpp" +#include "workers.hpp" + +#include +#include +#include +#include + +int main() { + const std::size_t num_philosophers = 4; + const std::size_t rounds = 3; + + th_safe::forks table(num_philosophers); + + std::vector philosophers; + for (auto id : std::views::iota(0UL, num_philosophers)) { + philosophers.emplace_back(workers::philosopher, id, std::ref(table), + rounds); + } +} diff --git a/semester-4/ОС/lb-8/src/task-3/workers.hpp b/semester-4/ОС/lb-8/src/task-3/workers.hpp new file mode 100644 index 0000000..1d134a8 --- /dev/null +++ b/semester-4/ОС/lb-8/src/task-3/workers.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "forks.hpp" + +#include +#include +#include +#include +#include +#include + +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 diff --git a/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.pdf b/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.pdf new file mode 100644 index 0000000..92a1967 Binary files /dev/null and b/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.pdf differ diff --git a/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.typ b/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.typ new file mode 100644 index 0000000..4a6490b --- /dev/null +++ b/semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.typ @@ -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 queue; + + std::vector 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 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 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 class queue { +public: + void push(const T &value) { + { + std::lock_guard lock(mutex_); + queue_.push(value); + } + cond_var_.notify_one(); + } + + std::optional 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 queue_; + std::mutex mutex_; + std::condition_variable cond_var_; + bool done_ = false; +}; +} // namespace th_safe +``` + +Виробники додають файли в чергу, а споживачі зчитують їхній вміст. + +```cpp +namespace workers { +void producer( + th_safe::queue &q, + const std::vector &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 &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 news; + std::atomic_bool done = false; + + std::jthread writer_thread([&] { + workers::writer(news, 10); + done = true; + }); + + std::vector 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 + class vector { + public: + void push_back(const T& value) { + std::unique_lock lock(mutex_); + data_.push_back(value); + } + + std::optional 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 data_; + }; +} +``` + +Письменник додає новини в вектор, а читачі зчитують з вектора останню додану новину. + +```cpp +namespace workers { +void writer( + th_safe::vector &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 &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 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 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( + ::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