1
0

Compare commits

...

3 Commits

Author SHA1 Message Date
Sytnyk Yehor 1e4d20b6a8 PRPA lb-4 2025-05-30 22:56:34 +03:00
Sytnyk Yehor 84f7393e78 PRPA lb-3 2025-05-30 22:56:25 +03:00
Sytnyk Yehor e79109c81a OS lb-8 2025-05-30 22:55:57 +03:00
51 changed files with 2318 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
> [!NOTE]
> Викладач: Мельникова Р. В.
>
> Оцінка: in progress
> [!TIP]
> Виконано для Linux в команді.
+21
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

+17
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
+73
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"
}
}
+28
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"
}
+35
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 \;
@@ -0,0 +1 @@
Hello from ./launcher/files/file1.txt
@@ -0,0 +1 @@
Hello from ./launcher/files/file2.txt
@@ -0,0 +1 @@
Hello from ./launcher/files/file3.txt
@@ -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;
}
@@ -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__");
}
}
@@ -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
@@ -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);
}
}
}
}
@@ -0,0 +1 @@
Hello from ./task-1/files/file1.txt
@@ -0,0 +1 @@
Hello from ./task-1/files/file2.txt
@@ -0,0 +1 @@
Hello from ./task-1/files/file3.txt
+36
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();
}
+45
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
@@ -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
+22
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);
}
}
@@ -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_;
};
}
@@ -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
+22
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
+20
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);
}
}
@@ -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
@@ -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
+6
View File
@@ -0,0 +1,6 @@
> [!NOTE]
> Викладач: Онищенко К. Г.; Афанасьєва І. В.
> Оцінка: In Progress
> [!TIP]
> Виконано в команді
+39
View File
@@ -0,0 +1,39 @@
title: "Проектування та розробка проекту. Метод мозкового штурму. Структурні діаграми: діаграма класів, об'єктів"
subject: ПП
doctype: ЛБ
worknumber: 3
mentors:
- name: Онищенко К. Г.,
gender: m,
degree: ст. викладач кафедри ПІ,
- 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
- name: Краснокутська Ю. Є.
course: 2
edu: *EDU
gender: f
group: 23-2
- name: Семьонов. О. О.
course: 2
edu: *EDU
gender: m
group: 23-2
- name: Петах С. І.
course: 2
edu: *EDU
gender: m
group: 23-2
+163
View File
@@ -0,0 +1,163 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
/' left to right direction '/
enum AttachmentType {
+ Description
+ DueDate
+ File
+ Url
+ Text
+ Tip
+ Hint
+ Warning
+ Progress
+ Importance
}
enum RequestType {
+ Add
+ Update
+ Remove
}
enum AccessLevel {
+ View
+ AddSolutions
+ Edit
+ AddUsers
+ FullAccess
}
enum MembershipLevel {
+ View
+ AddTasks
+ Edit
+ AddUsers
+ FullAccess
}
enum TaskVisibility {
+ Private
+ Public
+ Paid
}
enum SolutionType {
+ File
+ Url
+ Text
}
hide AccessLevel methods
hide MembershipLevel methods
hide AttachmentType methods
hide TaskVisibility methods
hide SolutionType methods
hide RequestType methods
class User {
- id : int
+ name : String
+ email : String
+ password : String
+ register()
+ login()
+ changePassword()
}
class Access {
- userId : int
- taskId : int
+ accessLevel : AccessLevel
+ changeAccessLevel()
}
class Task {
- id : int
+ attachments : List<Attachment>
+ solutions : List<Solution>
+ requests : List<Request>
+ name : String
+ visibility : TaskVisibility
+ create()
+ remove()
+ fork()
+ changeVisibility()
+ transferOwnership()
}
class Attachment {
+ type : AttachmentType
+ data : Blob
+ isPrivate: boolean
+ description : String
}
class Solution {
+ description: String
+ type: SolutionType
+ data: blob
+ approve()
+ decline()
}
class TaskGroup {
+ tasks : List<Task>
+ name : String
+ description : String
+ addTask()
+ removeTask()
+ addUser()
+ removeUser()
}
class Membership {
- userId : int
+ taskGroupId : int
+ membershipLevel: MembershipLevel
+ changeMembershipLevel()
}
class Request {
- id : int
- userId : int
- attachmentId : int
+ type : RequestType
+ message : String
+ isApplied
+ isRejected
+ apply()
+ reject()
+ revoke()
}
hide Attachment methods
User -u.> Access : <<manages>>
User -u.> Membership : <<manages>>
User -u.> Attachment : <<owns>>
User -u.> Request : <<supervises>>
User -u.> Solution : <<supervises>>
Task -d.> Access : <<grants>>
TaskGroup -d.> Membership : <<grants>>
Task -d-> Attachment : contains
Task -d-> Request : associates
Task -d-> Solution : associates
Request <-u- Attachment : associates
TaskGroup -u-> Task : manages
AttachmentType --* Attachment
RequestType -r-* Request
AccessLevel --* Access
MembershipLevel --* Membership
TaskVisibility --* Task
SolutionType -u-* Solution
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

+110
View File
@@ -0,0 +1,110 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
object "user1: User" as user1 {
id = 1
name = "Alice Smith"
email = "alice@example.com"
}
object "user2: User" as user2 {
id = 2
name = "Bob Johnson"
email = "bob@example.com"
}
object "proj_team_alpha: TaskGroup" as taskGroup1 {
name = "Project Alpha Team"
description = "Tasks for Project Alpha"
}
object "task_design_ui: Task" as task1 {
id = 101
name = "Design UI Mockups"
visibility = Public
}
object "task_refactor_code: Task" as task2 {
id = 102
name = "Refactor Legacy Code"
visibility = Private
}
object "attach_new_icon: Attachment" as attach1 {
type = File
description = "New icon suggestion"
isPrivate = false
}
object "sol_ui_mockups: Solution" as sol1 {
description = "Figma link to mockups"
type = Url
}
object "req_add_icon: Request" as req1 {
id = 201
userId = 2
attachmentId = 301
type = Add
message = "Proposing new icon for homepage."
isApplied = false
isRejected = false
}
object "access_ui_task1: Access" as access1 {
userId = 1
taskId = 101
accessLevel = FullAccess
}
object "access_ui_task2: Access" as access2 {
userId = 2
taskId = 101
accessLevel = View
}
object "access_refactor_task1: Access" as access3 {
userId = 2
taskId = 102
accessLevel = FullAccess
}
object "memb_user1_proj: Membership" as memb1 {
userId = 1
taskGroupId = 1
membershipLevel = FullAccess
}
object "memb_user2_proj: Membership" as memb2 {
userId = 2
taskGroupId = 1
membershipLevel = View
}
taskGroup1 -r- memb1
taskGroup1 -u---- memb2
memb1 -u- user1
memb2 -u- user2
taskGroup1 -u-- task1
access1 -d- user1
access2 -u- user2
access3 -l- user2
access1 -l- task1
access2 -d- task1
access3 -r- task2
sol1 -u- task1
sol1 -- user1
attach1 -- req1
attach1 -l- task1
req1 -- task1
req1 -u- user2
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

+225
View File
@@ -0,0 +1,225 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
left to right direction
!define Cloud(name, description) usecase "description" as name [[name]] #DarkGray
!define Kite(name, description) usecase "description" as name [[name]] #Yellow
!define Sea(name, description) usecase "description" as name [[name]] #LightBlue
!define Fish(name, description) usecase "description" as name [[name]] #LightGray
!define Clam(name, description) usecase "description" as name [[name]] #Silver
<style>
package {
LineColor transparent
BackgroundColor transparent
Shadowing 0
FontColor transparent
}
</style>
<style>
.visiblePackage {
LineColor black
Shadowing 1
FontColor black
}
</style>
hide <<visiblePackage>> stereotype
/' package "TaskHub" <<visiblePackage>> { '/
package "TaskHub" {
actor User as user
actor Guest as guest
actor Admin as admin
' Auth
package "Auth" {
actor "Google Auth Provider" as google
/' Cloud(auth, "Authentication") '/
/' guest '1' -- '' auth '/
Kite(auth_login, "Login")
Kite(auth_register, "Registration")
guest "1" --- "0..1" auth_login
guest "1" --- "0..*" auth_register
Sea(register_email, "Register via Email")
auth_register ..> register_email : <<include>>
Fish(enter_email, "Enter Email without confirmation")
Fish(enter_email_confirm, "Enter Email with confirmation")
Fish(enter_password, "Enter Passwounrd")
Fish(enter_username, "Enter Username")
register_email ..> enter_email_confirm : <<include>>
register_email ..> enter_password: <<include>>
register_email ..> enter_username : <<include>>
Sea(login_google, "Login via Google OAuth")
auth_login <.. login_google : <<extend>>
Fish(choose_account, "Choose Google account")
login_google ..> choose_account : <<include>>
choose_account "0..*" -- "1" google
Sea(login_email, "Login via Email")
auth_login <.. login_email: <<extend>>
login_email ..> enter_email : <<include>>
login_email ..> enter_password: <<include>>
Sea(recover_password, "Password Recovery")
auth_login <.. recover_password : <<extend>>
Fish(enter_password_new, "Enter New Password")
recover_password ..> enter_email_confirm : <<include>>
recover_password ..> enter_password_new : <<include>>
}
package "Own Task Managment" {
Kite(own_task_create, "Create new Task")
user "1" --- "0..*" own_task_create
Sea(own_task_create_name, "Enter Task Name")
Sea(own_task_create_visibility, "Select Task Visibility")
own_task_create ..> own_task_create_name : <<include>>
own_task_create ..> own_task_create_visibility : <<include>>
Kite(own_task_delete, "Delete Task")
user "1" --- "0..*" own_task_delete
Sea(own_task_delete_confirm, "Enter Confirmation Code form Email")
own_task_delete ..> own_task_delete_confirm : <<include>>
Kite(own_task_update, "Update Task Attachments")
user "1" --- "0..*" own_task_update
Sea(own_task_update_type, "Select Attachment Type")
Sea(own_task_update_content, "Select Attachment Content")
Sea(own_task_update_visibility, "Select Attachment visibility")
own_task_update ..> own_task_update_type : <<include>>
own_task_update ..> own_task_update_content : <<include>>
own_task_update ..> own_task_update_visibility : <<include>>
Kite(own_task_visibility, "Change visibility of the Task")
user "1" --- "0..*" own_task_visibility
Sea(own_task_visibility_private, "Set Private Task Visibility")
Sea(own_task_visibility_public, "Set Public Task Visibility")
Sea(own_task_visibility_paid, "Set Paid Task Visibility")
own_task_visibility <.. own_task_visibility_private : <<extend>>
own_task_visibility <.. own_task_visibility_public : <<extend>>
own_task_visibility <.. own_task_visibility_paid : <<extend>>
Kite(own_task_requests, "Manage Tasks Requests")
user "1" --- "0..*" own_task_requests
Sea(own_task_request_approve, "Approve Task Request")
Sea(own_task_request_decline, "Decline Task Request")
own_task_requests <.. own_task_request_approve : <<extend>>
own_task_requests <.. own_task_request_decline : <<extend>>
Kite(own_task_access, "Manage Task Access Rights")
user "1" --- "0..*" own_task_access
Sea(own_task_access_add, "Give Access for User")
Sea(own_task_access_remove, "Remove Access of the User")
Sea(own_task_access_update, "Update Access of the User")
own_task_access <.. own_task_access_add : <<extend>>
own_task_access <.. own_task_access_remove : <<extend>>
own_task_access <.. own_task_access_update : <<extend>>
/' } '/
package "Other Tasks Interactions" {
actor "Payment Provider" as payment
Kite(task_fork, "Fork existing Task")
user "1" --- "0..*" task_fork
Sea(task_fork_options, "Select Fork Options")
task_fork ..> task_fork_options : <<include>>
Kite(other_task_request_managment, "Manage Task Requests")
user "1" --- "0..*" other_task_request_managment
Sea(request_add, "Add Attachment Request")
Sea(request_update, "Update Attachment Request")
Sea(request_remove, "Remove Attachment Request")
Sea(request_revoke, "Revoke existing Request")
other_task_request_managment <.. request_add : <<extend>>
other_task_request_managment <.. request_update: <<extend>>
other_task_request_managment <.. request_remove : <<extend>>
other_task_request_managment <.. request_revoke : <<extend>>
Kite(buy_task, "Buy Paid Task")
user "1" --- "0..*" buy_task
Sea(select_payment, "Select Payment Method")
buy_task ..> select_payment : <<include>>
select_payment "0..*" -- "1" payment
}
package "Admin Panel" {
Kite(manage_users, "Manage Users")
admin "1" --- "0..*" manage_users
Sea(review_users, "Review User Details")
manage_users ..> review_users : <<include>>
Fish(create_user, "Create New User")
Fish(view_user_profile, "View Private Data")
Fish(delete_user_admin, "Delete User")
Fish(block_user_admin, "Block User")
review_users <.. create_user : <<extend>>
review_users <.. view_user_profile : <<extend>>
review_users <.. delete_user_admin : <<extend>>
review_users <.. block_user_admin : <<extend>>
Kite(manage_content, "Manage Content")
admin "1" --- "0..*" manage_content
Sea(review_content, "Review Content Details")
manage_content ..> review_content : <<include>>
Fish(approve_content, "Approve Content")
Fish(remove_content, "Remove Content")
Fish(warn_user, "Warn Content Author")
review_content <.. approve_content : <<extend>>
review_content <.. remove_content : <<extend>>
review_content <.. warn_user : <<extend>>
}
package "Task Group Management" {
actor "Github Integration Provider" as github
Kite(manage_tasks_in_groups, "Manage Tasks in Groups")
Kite(manage_user_access, "Manage User's Access")
Kite(manage_users_tg, "Manage Users")
Kite(tg_manage_github, "Manage Github Integration")
user "1" --- "0..*" manage_users_tg
user "1" --- "0..*" manage_tasks_in_groups
user "1" --- "0..*" manage_user_access
user "1" --- "0..*" tg_manage_github
Sea(add_user, "Add User")
Sea(delete_user, "Delete User")
manage_users_tg ..> add_user : <<include>>
manage_users_tg ..> delete_user : <<include>>
Sea(set_user_access, "Change Access Level")
manage_user_access ..> set_user_access : <<include>>
Sea(tg_add_task, "Add Task to Group")
Sea(tg_create_task, "Create new Task in Group")
Sea(tg_remove_task, "Remove Task from Group")
manage_tasks_in_groups <.. tg_add_task : <<extend>>
manage_tasks_in_groups <.. tg_create_task : <<extend>>
manage_tasks_in_groups <.. tg_remove_task : <<extend>>
Sea(github_link_repository, "Link GitHub repository to the Group")
Sea(github_unlink_repository, "Unlink GitHub repository from the Group")
tg_manage_github <.. github_link_repository : <<extend>>
tg_manage_github <.. github_unlink_repository : <<extend>>
github_link_repository "0..*" -- "1" github
github_unlink_repository "0..*" -- "1" github
}
}
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 98 KiB

@@ -0,0 +1,305 @@
#import "@local/nure:0.1.0": *
#import calc: max
#import table: cell
#show: pz-lb.with(..yaml("doc.yaml"))
#set text(size: 14pt)
#let map-column(items, min-height, column) = {
return for i in range(0, min-height) {
(items.at(i, default: ""),)
}.map(e => cell(x: column)[_ #e _])
}
#let crc-card(
name: "Name",
subclasses: (),
superclasses: (),
responsibilities: ("responsibilities",),
collaborators: (),
description: "description",
attributes: ("attributes",),
) = figure(
table(
align: left, column-gutter: (0cm, 0.5cm), columns: (3fr, 2fr, 4fr),
cell(x: 0, y: 0, colspan: 2)[_ #name _],
cell(x: 0, y: 1, colspan: 2)[subclasses: _ #subclasses.join(", ") _],
cell(x: 0, y: 2, colspan: 2)[superclasses: _ #superclasses.join(", ") _],
table.cell(x: 0, y: 3)[responsibilities],
..map-column(responsibilities, 5, 0),
cell(x: 1, y: 3)[collaborators],
..map-column(collaborators, 5, 1),
cell(x: 2, y: 0)[_ #name _],
cell(x: 2, y: 1)[#linebreak()],
cell(x: 2, y: 2)[Description:],
cell(x: 2, y: 3)[_ #description _],
cell(x: 2, y: 4)[Attributes:],
..map-column(attributes, 4, 2),
),
)
#let crc-cards = (
// User
(
name: "User",
description: "Represents a platform user with authentication, profile management, and subscription capabilities",
attributes: (
"email",
"username",
"password",
"profileInfo",
"subscriptionTier",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Authenticate user credentials",
"Manage user profile information",
"Track subscription status and limits",
"Maintain user activity history",
"Handle user preferences and settings",
),
collaborators: (
"Task",
"Solution",
"Attachment",
"Payment",
"Subscription",
),
),
// Task
(
name: "Task",
description: "Core entity representing a task with description, attachments, access controls, and monetization options",
attributes: (
"title",
"owner",
"tags",
"attachments",
"accessType",
"price",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Store task information and metadata",
"Manage task access permissions",
"Handle task pricing and monetization",
"Track task completion status",
"Maintain task versioning and history",
"Support task cloning/forking",
"Manage attached files and resources",
),
collaborators: (
"User",
"Solution",
"TaskGroup",
"Comment",
"Payment",
"Attachment",
),
),
// Attachment
(
name: "Attachment",
description: "Manages attachments for tasks and solutions with storage limits and security controls",
attributes: (
"creator",
"target",
"type",
"data",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Enforce storage limits per subscription tier",
"Handle file upload and download",
"Support multiple formats",
"Generate secure URLs",
),
collaborators: (
"Task",
"Solution",
"User",
"Subscription",
),
),
// Solution
(
name: "Solution",
description: "Represents user-submitted solutions to tasks with various formats and evaluation capabilities",
attributes: (
"task",
"owner",
"content",
"attachments",
"status",
"rating",
"isPublic",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Store solution content and attachments",
"Track solution submission status",
"Handle solution evaluation and rating",
"Manage solution visibility settings",
"Support various solution formats",
"Maintain solution history",
),
collaborators: (
"User",
"Task",
"Comment",
"Rating",
"Attachment",
),
),
// TaskGroup
(
name: "TaskGroup",
description: "Organizes related tasks into thematic or project-based collections with shared access settings",
attributes: (
"name",
"description",
"owner",
"tasks",
"accessType",
"price",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Group related tasks together",
"Manage group-level access permissions",
"Handle group monetization settings",
"Integrate with GitHub repositories",
"Support bulk operations on tasks",
"Maintain group metadata",
),
collaborators: (
"Task",
"User",
"Payment",
),
),
// Comment
(
name: "Comment",
description: "Enables user interaction through comments on tasks with rating capabilities",
attributes: (
"user",
"target",
"content",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Store comment content and metadata",
"Track comment timestamps",
"Handle comment moderation",
),
collaborators: (
"User",
"Task",
"Solution",
),
),
// Payment
(
name: "Payment",
description: "Handles financial transactions for task access, solutions, and subscriptions",
attributes: (
"user",
"target",
"amount",
"currency",
"status",
"paymentMethod",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Process payment transactions",
"Validate payment information",
"Handle payment failures and retries",
"Track payment history",
"Integrate with payment providers",
"Manage refunds and chargebacks",
),
collaborators: (
"User",
"Task",
"TaskGroup",
"Subscription",
),
),
// Subscription
(
name: "Subscription",
description: "Manages user subscription tiers, limits, and billing cycles",
attributes: (
"user",
"tier",
"startDate",
"endDate",
"status",
"taskLimit",
"storageLimit",
"price",
"billingCycle",
),
subclasses: (),
superclasses: (),
responsibilities: (
"Manage subscription lifecycle",
"Enforce subscription limits",
"Handle subscription upgrades/downgrades",
"Track usage against limits",
"Process subscription renewals",
),
collaborators: (
"User",
"Payment",
),
),
)
#v(-spacing)
== Мета роботи
Отримати навички побудови статичного представлення логічної моделі
проектованої інформаційної системи у вигляді CRC-карток, об'єктної моделі,
діаграми класів UML.
== Хід роботи
#v(-spacing)
=== Створення Smart Use Case діаграми
SMART Use Case Diagram це один з інструментів моделювання в UML, що візуально відображає функціональні вимоги до системи з точки зору користувача. Вона складається з акторів, варіантів використання та зв'язків між ними, демонструючи, хто і як використовує систему для досягнення певних цілей. Ця діаграма допомагає розробникам, замовникам та іншим зацікавленим сторонам краще зрозуміти обсяг та призначення системи, сприяючи ефективній комунікації та збору вимог на ранніх етапах розробки програмного забезпечення.
#figure(image("uml/uc.svg"), caption: [SMART Use Case діаграма])
=== Проєктування системи методом мозкового штурму (CRC-картки)
CRC-картки це інструмент для об'єктно-орієнтованого дизайну, що використовується переважно на початкових етапах розробки програмного забезпечення. Вони допомагають команді швидко обговорювати та визначати, які класи потрібні системі, за що відповідає кожен клас, і з якими іншими класами він взаємодіє для виконання своїх завдань.
Під час мозкового штурму нами було виділено картки із наступними класами:
#crc-cards.map(e => e.name).join(", ");
#crc-cards.map(e => crc-card(..e)).join(line(length: 100%, stroke: 1pt + gray));
=== Створення діаграми класів
Діаграма класів показує класи, їхні атрибути та операції а також зв'язки між ними. Це основна діаграма, що визначає архітектуру системи та взаємодію її компонентів.
#figure(image("uml/class.svg"), caption: [Діаграма класів])
=== Створення діаграми об'єктів
Діаграма об'єктів є миттєвим станом системи в конкретний момент часу, що демонструє конкретні екземпляри класів (об'єкти) та їхні зв'язки. На відміну від діаграми класів, яка показує загальну структуру та можливі зв'язки, діаграма об'єктів відображає реальні об'єкти з їхніми поточними значеннями атрибутів і фактичними зв'язками між ними. Вона використовується для візуалізації конкретних сценаріїв виконання та допомагає перевірити правильність діаграми класів.
#figure(image("uml/object.svg"), caption: [Діаграма об'єктів])
== Висновки
За результатами цієї лабораторної роботи ми отримали навички побудови статичного представлення логічної моделі
проектованої інформаційної системи у вигляді CRC-карток, об'єктної моделі та діаграми класів UML.
+6
View File
@@ -0,0 +1,6 @@
> [!NOTE]
> Викладач: Онищенко К. Г.; Афанасьєва І. В.
> Оцінка: In Progress
> [!TIP]
> Виконано в команді
+39
View File
@@ -0,0 +1,39 @@
title: "Структурні діаграми компонентів та розгортання."
subject: ПП
doctype: ЛБ
worknumber: 4
mentors:
- name: Онищенко К. Г.,
gender: m,
degree: ст. викладач кафедри ПІ,
- 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
- name: Краснокутська Ю. Є.
course: 2
edu: *EDU
gender: f
group: 23-2
- name: Семьонов. О. О.
course: 2
edu: *EDU
gender: m
group: 23-2
- name: Петах С. І.
course: 2
edu: *EDU
gender: m
group: 23-2
+45
View File
@@ -0,0 +1,45 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
left to right direction
package "Backend Server" as backend {
[App.rs] --> [Router.rs]
[App.rs] --> [Hooks.rs]
[Router.rs] --> [Controllers.rs]
[Router.rs] --> [DataViews.rs]
[Controllers.rs] --> [DataModels.rs]
[Controllers.rs] --> [Mailers.rs]
[Controllers.rs] -r-> [DataViews.rs]
[Controllers.rs] -l-> [APIProviders.rs]
[DataModels.rs] -l-> [DatabaseEntities.rs]
}
package "Database" as db {
[DatabaseEntities.rs] --> [DatabaseMigrations.rs]
}
package "Frontend Server" as frontend {
[main.tsx] -u-> [routes]
[routes] -u-> [components]
[components] -u-> [hooks]
[hooks] -u-> [store]
[hooks] -u-> [api]
[api] -r-> [types]
[store] -l-> [types]
}
package "Mobile App" {
[MainActivity.kt] -u-> [Fragments.kt]
[Fragments.kt] -u-> [ViewModels.kt]
[Fragments.kt] -r-> [Adapters.kt]
[ViewModels.kt] -u-> [APIClient.kt]
[APIClient.kt] -r-> [DataModels.kt]
[DataModels.kt] -l-> [ViewModels.kt]
}
[App.rs] -u- [APIClient.kt] : Rest
[App.rs] -u- [api] : Rest
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,81 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
node backend [
<b>Backend Server Instance(s)</b>
---
App.rs
....
Router.rs
....
Hooks.rs
....
Controllers.rs
....
DataViews.rs
....
DataModels.rs
....
Mailers.rs
....
APIProviders.rs
....
APIProviders.rs
....
DatabaseEntities.rs
....
DatabaseMigrations.rs
]
node db_server [
<b>Database Server Instance(s)</b>
---
Actual PostgreSQL Database
]
node frontend_host [
<b>Frontend Hosting/CDN</b>
---
main.tsx
....
routes
....
components
....
hooks
....
store
....
api
....
types
]
node workstation [
<b>User Workstation</b>
]
node mobile_devices [
<b>User Mobile Devices</b>
---
MainActivity.kt
....
Fragments.kt
....
ViewModels.kt
....
Adapters.kt
....
APIClient.kt
....
DataModels.kt
]
backend -- db_server : TCP/IP - ORM Connection
frontend_host -u-> workstation : HTTPS - Serves Assets
workstation --> backend : HTTPS - REST API Calls
mobile_devices --> backend : HTTPS - REST API Calls
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

+96
View File
@@ -0,0 +1,96 @@
@startuml
skin rose
skinparam backgroundColor #EEEBDC
hide empty description
[*] --> Registration
state Registration {
Registration --> Email_Validation
Registration --> Google_Authentication
Email_Validation : register()
Email_Validation : send_confirmation_email()
Email_Validation : validate_email()
Google_Authentication : login_with_google()
Google_Authentication : validate_google_token()
Email_Validation --> User_Registered
Google_Authentication --> User_Registered
}
User_Registered --> Login
state Login {
Login --> Credentials_Validation
Login --> Google_Authentication_Login
Credentials_Validation : validate_login()
Credentials_Validation : validate_password()
Google_Authentication_Login : login_via_google()
Credentials_Validation --> User_LoggedIn
Google_Authentication_Login --> User_LoggedIn
}
state User_LoggedIn {
state Main_Menu {
Main_Menu --> Task
Main_Menu --> Task_Group
}
Main_Menu : view_tasks()
Main_Menu : view_task_groups()
Main_Menu : search_tasks(keyword)
Main_Menu : filter_tasks_by_access(access_type)
state Task {
Task --> Create_Task
Task --> View_Task
Task --> Edit_Task
Task --> Delete_Task
}
Task : create(title, description)
Task : edit(task_id, new_title, new_description)
Task : delete(task_id)
Task : view(task_id)
Task : set_access(task_id, access_type)
Task : add_attachment(task_id, file)
Task : download_attachment(task_id, file_id)
Task : get_attachment_list(task_id)
Task : view_history(task_id)
Task : remove_attachment(task_id, file_id)
Create_Task --> Set_Task_Access
Edit_Task --> Set_Task_Access
state Set_Task_Access {
Set_Task_Access --> Public_Access
Set_Task_Access --> Private_Access
Set_Task_Access --> Paid_Access
}
Public_Access : access = public
Private_Access : access = private
Paid_Access : access = paid
state Task_Group {
Task_Group --> Create_Group
Task_Group --> View_Group
Task_Group --> Edit_Group
Task_Group --> Delete_Group
}
Task_Group : create_group(name)
Task_Group : add_task_to_group(task_id)
Task_Group : remove_task_from_group(task_id)
Task_Group : delete_group(group_id)
Task_Group : rename_group(group_id, new_name)
Task_Group : view_group(group_id)
}
User_LoggedIn --> [*]
@enduml
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

@@ -0,0 +1,24 @@
#import "@local/nure:0.1.0": *
#show: pz-lb.with(..yaml("doc.yaml"))
#v(-spacing)
== Мета роботи
Метою даної лабораторної роботи є вивчення моделювання структурних діаграм компонентів та розгортання
за допомогою мови UML, та закріплення отриманих навички методом моделювання на прикладі обраної теми проекту.
== Хід роботи
Діаграма станів -- це одна з поведінкових діаграм у UML, яка описує можливі стани об'єкта та переходи між ними протягом його життєвого циклу. Вона візуалізує динамічну поведінку системи або її компонента, показуючи, як об'єкт реагує на події, змінюючи свій стан. Кожен стан представляє певний етап у житті об'єкта, а переходи між станами ініціюються подіями та можуть супроводжуватися діями.
Діаграма компонентів -- це одна з поведінкових діаграм у UML, яка візуалізує архітектуру системи, показуючи її компоненти, їхні інтерфейси та залежності між ними. Компонент у цьому контексті є модульною, замінною частиною системи, яка інкапсулює певну функціональність і надає її через чітко визначені інтерфейси, а також може вимагати певні інтерфейси від інших компонентів. Діаграми компонентів допомагають зрозуміти, як різні частини програмного забезпечення організовані та взаємодіють між собою, що є важливим для розгортання, підтримки та подальшого розвитку системи. Вони дозволяють моделювати фізичну структуру системи, відображаючи її на рівні програмних модулів, бібліотек, виконуваних файлів або баз даних.
Діаграма розгортання -- це ще один тип структурних діаграм у UML, яка показує фізичне розміщення артефактів (таких як виконувані файли, бібліотеки, документи тощо) на вузлах (фізичних апаратних пристроях або середовищах виконання). Вона візуалізує конфігурацію апаратних засобів і програмного забезпечення, що розгортається в системі. На цій діаграмі вузли представляють обчислювальні ресурси (наприклад, сервери, робочі станції, мережеві пристрої), а зв'язки між ними показують комунікаційні шляхи. Усередині вузлів розміщуються артефакти, що позначають конкретні програмні компоненти, які виконуватимуться на цих вузлах. Діаграми розгортання є незамінними для архітекторів систем та інженерів DevOps, оскільки вони допомагають планувати, документувати та візуалізувати інфраструктуру, необхідну для функціонування програмного забезпечення, а також розуміти, як різні частини системи розподілені в реальному фізичному середовищі.
#figure(image("uml/state.svg", width: 90%), caption: [Діаграма станів])
#figure(image("uml/component.svg", width: 75%), caption: [Діаграма компонентів])
#figure(image("uml/distribution.svg", width: 63%), caption: [Діаграма розгортання])
== Висновки
За результатами цієї лабораторної роботи ми навчились моделювати структурні діаграм компонентів та розгортання
за допомогою мови UML, та закріпили отримані навички методом моделювання на прикладі обраної теми проекту.