OS lb-8
This commit is contained in:
7
semester-4/ОС/lb-8/README.md
Normal file
7
semester-4/ОС/lb-8/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Мельникова Р. В.
|
||||
>
|
||||
> Оцінка: in progress
|
||||
|
||||
> [!TIP]
|
||||
> Виконано для Linux в команді.
|
21
semester-4/ОС/lb-8/doc.yaml
Normal file
21
semester-4/ОС/lb-8/doc.yaml
Normal 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
|
BIN
semester-4/ОС/lb-8/img/task-1-result.png
Normal file
BIN
semester-4/ОС/lb-8/img/task-1-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 KiB |
BIN
semester-4/ОС/lb-8/img/task-2-result.png
Normal file
BIN
semester-4/ОС/lb-8/img/task-2-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
BIN
semester-4/ОС/lb-8/img/task-3-result.png
Normal file
BIN
semester-4/ОС/lb-8/img/task-3-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
BIN
semester-4/ОС/lb-8/img/task-4-result.png
Normal file
BIN
semester-4/ОС/lb-8/img/task-4-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
17
semester-4/ОС/lb-8/src/.clangd
Normal file
17
semester-4/ОС/lb-8/src/.clangd
Normal 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
semester-4/ОС/lb-8/src/.vscode/settings.json
vendored
Normal file
73
semester-4/ОС/lb-8/src/.vscode/settings.json
vendored
Normal 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
semester-4/ОС/lb-8/src/.vscode/tasks.json
vendored
Normal file
28
semester-4/ОС/lb-8/src/.vscode/tasks.json
vendored
Normal 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
semester-4/ОС/lb-8/src/Makefile
Normal file
35
semester-4/ОС/lb-8/src/Makefile
Normal 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 \;
|
1
semester-4/ОС/lb-8/src/launcher/files/file1.txt
Normal file
1
semester-4/ОС/lb-8/src/launcher/files/file1.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file1.txt
|
1
semester-4/ОС/lb-8/src/launcher/files/file2.txt
Normal file
1
semester-4/ОС/lb-8/src/launcher/files/file2.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file2.txt
|
1
semester-4/ОС/lb-8/src/launcher/files/file3.txt
Normal file
1
semester-4/ОС/lb-8/src/launcher/files/file3.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file3.txt
|
23
semester-4/ОС/lb-8/src/launcher/launcher.cpp
Normal file
23
semester-4/ОС/lb-8/src/launcher/launcher.cpp
Normal 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;
|
||||
}
|
40
semester-4/ОС/lb-8/src/launcher/main.cpp
Normal file
40
semester-4/ОС/lb-8/src/launcher/main.cpp
Normal 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__");
|
||||
}
|
||||
}
|
93
semester-4/ОС/lb-8/src/launcher/queue.hpp
Normal file
93
semester-4/ОС/lb-8/src/launcher/queue.hpp
Normal 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
|
34
semester-4/ОС/lb-8/src/launcher/workers.hpp
Normal file
34
semester-4/ОС/lb-8/src/launcher/workers.hpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
semester-4/ОС/lb-8/src/task-1/files/file1.txt
Normal file
1
semester-4/ОС/lb-8/src/task-1/files/file1.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file1.txt
|
1
semester-4/ОС/lb-8/src/task-1/files/file2.txt
Normal file
1
semester-4/ОС/lb-8/src/task-1/files/file2.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file2.txt
|
1
semester-4/ОС/lb-8/src/task-1/files/file3.txt
Normal file
1
semester-4/ОС/lb-8/src/task-1/files/file3.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file3.txt
|
36
semester-4/ОС/lb-8/src/task-1/main.cpp
Normal file
36
semester-4/ОС/lb-8/src/task-1/main.cpp
Normal 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
semester-4/ОС/lb-8/src/task-1/queue.hpp
Normal file
45
semester-4/ОС/lb-8/src/task-1/queue.hpp
Normal 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
|
40
semester-4/ОС/lb-8/src/task-1/workers.hpp
Normal file
40
semester-4/ОС/lb-8/src/task-1/workers.hpp
Normal 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
|
22
semester-4/ОС/lb-8/src/task-2/main.cpp
Normal file
22
semester-4/ОС/lb-8/src/task-2/main.cpp
Normal 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);
|
||||
}
|
||||
}
|
32
semester-4/ОС/lb-8/src/task-2/vector.hpp
Normal file
32
semester-4/ОС/lb-8/src/task-2/vector.hpp
Normal 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_;
|
||||
};
|
||||
}
|
47
semester-4/ОС/lb-8/src/task-2/workers.hpp
Normal file
47
semester-4/ОС/lb-8/src/task-2/workers.hpp
Normal 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
|
22
semester-4/ОС/lb-8/src/task-3/forks.hpp
Normal file
22
semester-4/ОС/lb-8/src/task-3/forks.hpp
Normal 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
semester-4/ОС/lb-8/src/task-3/main.cpp
Normal file
20
semester-4/ОС/lb-8/src/task-3/main.cpp
Normal 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);
|
||||
}
|
||||
}
|
43
semester-4/ОС/lb-8/src/task-3/workers.hpp
Normal file
43
semester-4/ОС/lb-8/src/task-3/workers.hpp
Normal 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
|
BIN
semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.pdf
Normal file
BIN
semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.pdf
Normal file
Binary file not shown.
489
semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.typ
Normal file
489
semester-4/ОС/lb-8/Лр_8_Ситник_Малишкін_ПЗПІ_23_2.typ
Normal 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
|
Reference in New Issue
Block a user