1
0
This commit is contained in:
Sytnyk Yehor
2025-05-15 20:51:24 +03:00
parent 24939470e5
commit 62049428c0
16 changed files with 961 additions and 0 deletions

View File

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

View File

@ -0,0 +1,21 @@
title: Керування процесами та потоками
subject: ОС
doctype: ЛБ
worknumber: 7
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: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,19 @@
cc := "clang"
cc_flags := "-Wall -Wextra -ggdb"
@clean:
rm -rf build
@mkdir-build: clean
mkdir -p build
@build-editor: mkdir-build
{{cc}} {{cc_flags}} -o build/main editor/main.c
{{cc}} {{cc_flags}} -o build/editor editor/editor.c
{{cc}} {{cc_flags}} -o build/analizer editor/analizer.c
@build-threads: mkdir-build
{{cc}} {{cc_flags}} -o build/main threads/main.c
@build-matrix-calc: mkdir-build
{{cc}} {{cc_flags}} -o build/main matrix-calc/main.c

View File

@ -0,0 +1,224 @@
#define _XOPEN_SOURCE
#include <time.h>
#include <fcntl.h>
#include <fts.h>
#include <iconv.h>
#include <stdio.h>
#include <stdlib.h>
#include "shared.h"
typedef enum { ENC_UTF8, ENC_UTF16LE, ENC_UTF16BE, ENC_CP1251 } Encoding;
Encoding get_encoding(char *input, size_t length) {
unsigned char *data = (unsigned char *)input;
if (length >= 2) {
if (data[0] == 0xFE && data[1] == 0xFF) { // UTF-16 BE BOM
return ENC_UTF16BE;
} else if (data[0] == 0xFF && data[1] == 0xFE) { // UTF-16 LE BOM
return ENC_UTF16LE;
}
}
size_t i = 0;
while (i < length) {
unsigned char c = data[i];
if (c < 0x80) { // ASCII
i++;
continue;
}
if ((c & 0xE0) == 0xC0) { // 2-byte UTF8
if (i + 1 >= length || (data[i + 1] & 0xC0) != 0x80 || c < 0xC2)
return ENC_CP1251; // overflow
i += 2;
continue;
}
if ((c & 0xF0) == 0xE0) { // 3-byte UTF8
if (i + 2 >= length || (data[i + 1] & 0xC0) != 0x80 ||
(data[i + 2] & 0xC0) != 0x80 || (c == 0xE0 && data[i + 1] < 0xA0))
return ENC_CP1251; // overflow
i += 3;
continue;
}
if ((c & 0xF8) == 0xF0) { // 4-byte UTF8
if (i + 3 >= length || (data[i + 1] & 0xC0) != 0x80 ||
(data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80 ||
(c == 0xF0 && data[i + 1] < 0x90))
return ENC_CP1251; // overflow
i += 4;
continue;
}
return ENC_CP1251; // Invalid utf-8 byte
}
return ENC_UTF8;
}
char *to_utf8(char *input, size_t length, Encoding encoding) {
if (!input || length == 0)
return NULL;
char *encstr;
int skip_bom = 0;
switch (encoding) {
case ENC_CP1251:
encstr = "CP1251";
break;
case ENC_UTF8:
encstr = "UTF-8";
break;
case ENC_UTF16LE:
encstr = "UTF-16LE";
skip_bom = 2;
break;
case ENC_UTF16BE:
encstr = "UTF-16BE";
skip_bom = 2;
break;
}
iconv_t cd = iconv_open("UTF-8", encstr);
if (cd != (iconv_t)-1) {
size_t out_size = length * 4 + 1;
char *output = malloc(out_size);
if (output) {
char *in_ptr = (char *)input + skip_bom;
size_t in_left = length - skip_bom;
char *out_ptr = output;
size_t out_left = out_size - 1;
if (iconv(cd, &in_ptr, &in_left, &out_ptr, &out_left) != (size_t)-1) {
*out_ptr = '\0';
iconv_close(cd);
return output;
}
free(output);
}
iconv_close(cd);
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc > 2) {
fprintf(stderr, "Error: too many arguments.\n");
return 1;
}
if (argc < 2) {
fprintf(stderr, "Error: too few arguments.\n");
return 1;
}
struct tm tm = {0};
if (strptime(argv[1], "%s", &tm) == NULL) {
fprintf(stderr, "Error: please provide time in UNIX timestamp format.\n");
return 1;
}
time_t norm_time = mktime(&tm);
char *paths[] = {CWD, NULL};
FTS *fts = fts_open(paths, FTS_NOCHDIR, NULL);
if (fts == NULL) {
err("Can't initialise FTS", __LINE__ - 1);
return 2;
}
char *buf = (char *)malloc(1024 * sizeof(char));
for (FTSENT *ent = fts_read(fts); ent != NULL; ent = fts_read(fts)) {
switch (ent->fts_info) {
case FTS_F:
if (ent->fts_statp->st_mtime > norm_time) {
printf(RED);
printf("--------------------------------\n");
printf(GREEN);
printf("File: %s\n", ent->fts_name);
char mtime[1024] = {0};
strftime(mtime, 1024, "%T", localtime(&ent->fts_statp->st_mtime));
printf("Modified at: %s\n", mtime);
printf("Size: %ld\n", ent->fts_statp->st_size);
FILE *file = fopen(ent->fts_path, "r");
if (file == NULL) {
err("Can't open the file", __LINE__ - 1);
break;
}
size_t bytes_read = fread(buf, 1, 1023, file);
if (ferror(file) != 0) {
err("Can't read the file", __LINE__ - 2);
fclose(file);
break;
}
buf[bytes_read] = '\0';
Encoding enc = get_encoding(buf, bytes_read);
printf("Lines length:");
size_t lines = 0;
size_t line_len = 0;
for (size_t i = (enc == 2 || enc == 1) ? 2 : 0; i < bytes_read; i++) {
line_len++;
int newline_detected = 0;
switch (enc) {
case ENC_UTF16BE:
if (i + 1 < bytes_read && buf[i] == '\0' && buf[i + 1] == '\n') {
newline_detected = 1;
line_len++;
i++;
}
break;
case ENC_UTF16LE:
if (i + 1 < bytes_read && buf[i] == '\n' && buf[i + 1] == '\0') {
newline_detected = 1;
line_len++;
i++;
}
break;
case ENC_CP1251:
case ENC_UTF8:
default:
if (buf[i] == '\n')
newline_detected = 1;
break;
}
if (newline_detected) {
printf(" %zu", line_len);
lines++;
line_len = 0;
}
}
printf("\nTotal lines: %zu\n", lines);
if (enc != ENC_UTF8)
buf = to_utf8(buf, bytes_read, enc);
printf(NORMAL);
printf("%s\n", buf);
fclose(file);
}
break;
default:
break;
}
}
free(buf);
return 0;
}

View File

@ -0,0 +1,31 @@
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include "shared.h"
int main() {
char *editor = getenv("EDITOR");
if (editor == NULL)
editor = "vi";
struct stat st = {0};
if (stat(CWD, &st) == -1) {
if (mkdir(CWD, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
err("Can't create directory for files", __LINE__ - 1);
return 1;
}
}
if (chdir(CWD) == -1) {
err("Can't change working directory", __LINE__ - 1);
return 2;
}
if (execlp(editor, editor, (char *)NULL)) {
err("Can't launch editor", __LINE__ - 1);
return 3;
}
return 0;
}

View File

@ -0,0 +1,39 @@
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "shared.h"
int main(void) {
time_t t = time(NULL);
char s[256];
strftime(s, 256, "%s", localtime(&t));
__pid_t e_pid = fork();
if (e_pid == -1) {
err("Can't fork process for editor", __LINE__ - 1);
return 1;
}
if (!e_pid && execl("build/editor", "build/editor", (char *)NULL)) {
err("Can't launch build/editor", __LINE__ - 1);
return 2;
}
waitpid(e_pid, 0, 0);
__pid_t a_pid = fork();
if (a_pid == -1) {
err("Can't fork process for analizer", __LINE__ - 1);
return 3;
}
if (!a_pid && execl("build/analizer", "build/analizer", s, (char *)NULL)) {
err("Can't launch build/analizer", __LINE__ - 1);
return 4;
}
waitpid(a_pid, 0, 0);
return 0;
}

View File

@ -0,0 +1,16 @@
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#define CWD "/tmp/files-dir"
#define RED "\x1b[31m"
#define GREEN "\x1b[32m"
#define NORMAL "\x1b[0m\n"
#define err(msg, line) \
do { \
char buf[256]; \
snprintf(buf, sizeof(buf), "%s:%d: error: %s", __FILE__, line, msg); \
perror(buf); \
} while (0)

View File

@ -0,0 +1,150 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MATRIX_SIZE 512
typedef struct {
double **a;
double **b;
double **r;
int start_row;
int end_row;
int size;
} ThreadArgs;
void mul_seq(double **a, double **b, double **r, int size) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
r[i][j] = 0;
for (int k = 0; k < size; k++)
r[i][j] += a[i][k] * b[k][j];
}
}
}
void *mul_par_worker(void *args) {
ThreadArgs *t_args = (ThreadArgs *)args;
double **a = t_args->a;
double **b = t_args->b;
double **r = t_args->r;
int start_row = t_args->start_row;
int end_row = t_args->end_row;
int size = t_args->size;
for (int i = start_row; i < end_row; i++) {
for (int j = 0; j < size; j++) {
r[i][j] = 0;
for (int k = 0; k < size; k++)
r[i][j] += a[i][k] * b[k][j];
}
}
pthread_exit(NULL);
}
void mul_par(double **a, double **b, double **r, int size, int n) {
pthread_t threads[n];
ThreadArgs args[n];
int rows_per_thread = size / n;
int rem_rows = size % n;
int curr_row = 0;
for (int i = 0; i < n; i++) {
args[i].a = a;
args[i].b = b;
args[i].r = r;
args[i].start_row = curr_row;
args[i].end_row = curr_row + rows_per_thread + (i < rem_rows ? 1 : 0);
args[i].size = size;
if (pthread_create(&threads[i], NULL, mul_par_worker, &args[i]) != 0) {
perror("Can't creat thread");
exit(EXIT_FAILURE);
}
curr_row = args[i].end_row;
}
for (int i = 0; i < n; i++) {
if (pthread_join(threads[i], NULL) != 0) {
perror("Can't join thread");
exit(EXIT_FAILURE);
}
}
}
double **alloc_matrix(int size) {
double **matrix = (double **)malloc(size * sizeof(double *));
for (int i = 0; i < size; i++)
matrix[i] = (double *)malloc(size * sizeof(double));
return matrix;
}
void free_matrix(double **matrix, int size) {
for (int i = 0; i < size; i++)
free(matrix[i]);
free(matrix);
}
void init_matrix(double **matrix, int size) {
for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++)
matrix[i][j] = (double)rand() / RAND_MAX * 10.0;
}
typedef void (*mult_func_seq)(double **, double **, double **, int);
typedef void (*mult_func_par)(double **, double **, double **, int, int);
double measure_time_seq(mult_func_seq func, double **a, double **b, double **r,
int size) {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
func(a, b, r, size);
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}
double measure_time_par(mult_func_par func, double **a, double **b, double **r,
int size, int n) {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
func(a, b, r, size, n);
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}
int main() {
double **a = alloc_matrix(MATRIX_SIZE);
double **b = alloc_matrix(MATRIX_SIZE);
double **r_seq = alloc_matrix(MATRIX_SIZE);
double **r_par = alloc_matrix(MATRIX_SIZE);
srand(time(NULL));
init_matrix(a, MATRIX_SIZE);
init_matrix(b, MATRIX_SIZE);
printf("performing sequentional multiplication...\n");
double sequential_time = measure_time_seq(mul_seq, a, b, r_seq, MATRIX_SIZE);
printf("time: %f secinds\n", sequential_time);
int num_threads = 32;
printf("\nperforming parrallel multiplication (%d threads)...\n",
num_threads);
double parallel_time =
measure_time_par(mul_par, a, b, r_par, MATRIX_SIZE, num_threads);
printf("time: %f seconds\n", parallel_time);
free_matrix(a, MATRIX_SIZE);
free_matrix(b, MATRIX_SIZE);
free_matrix(r_seq, MATRIX_SIZE);
free_matrix(r_par, MATRIX_SIZE);
return 0;
}

View File

@ -0,0 +1,29 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 10
void *thread_function(void *thread_id) {
long tid = (long)thread_id;
printf("Begin\t%ld\n", tid);
for (int i = 0; i < 100000; ++i)
;
printf("End\t%ld\n", tid);
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
long t;
for (t = 0; t < NUM_THREADS; t++)
pthread_create(&threads[t], NULL, thread_function, (void *)t);
void *status;
pthread_join(threads[0], &status);
printf("Main thread completed execution\n");
return 0;
}

View File

@ -0,0 +1,425 @@
#import "@local/nure:0.1.0": *
#show: pz-lb.with(..yaml("doc.yaml"))
#v(-spacing)
== Мета роботи
Метою даної лабораторної роботи є вивчення створення процесів та потоків при виконанні
програм.
== Хід роботи
Дану лабораторну роботу нами було виконано для операційної системи Linux із
використанням POSIX API.
=== Завдання 1 -- програмний комплекс для створення та аналізу текстових файлів.
Метою цього завдання є дослідження створення дочірніх процесів із своєї програми.
Завдання полягає у розробці трьох програм:
+ програма 1 запускає текстовий редактор у заданій папці;
+ програма 2 аналізує файли у заданій директорії, створені після вказаного часу,
визначаючи для них розмір, кількість рядків та довжину кожного рядка, з підтримкою
ASCII та UNICODE кодувань;
+ програма 3 запускає спочатку програму 1, а потім програму 2, передаючи час запуску
першої програми як параметр для другої.
==== Розробка програми 1
Програма 1 повинна запустити системний текстовий редактор за замовчуванням у вказаній
директорії.
Для цього необхідно:
+ визначити редактор за замовчуванням;
+ створити вказану директорію, якщо вона не існує;
+ запустити редактор у вказаній директорії.
Назва текстового редактору за замовчуванням на POSIX сумісних операційних системах
зазвичай зберігається в змінній оточення "EDITOR", отримати її значення можна за
допомогою функції "getenv" стандартної бібліотеки. Ця функція повертає вказівник на
рядок, що відповідає значенню змінної, або "NULL", якщо ця змінна відсутня.
Скористаємося цим, щоб встановити редактор за замовчуванням.
Щоб визначити чи директорія для файлів існує скористаємося системним викликом
"stat" -- якщо він завершиться із помилкою, значить директорії не існує, та її
треба створити. Для створення директорії в разі її відсутності використаємо
системний виклик "mkdir".
В POSIX сумісних операційних системах для запуску зовнішніх програм використовується
системний виклик "exec". Цей системний виклик замінює образ поточного процесу на новий,
а не створює новий процес, тому щоб відкрити редактор у вказаній директорії необхідно
зміни робочу директорію поточного процесу. Для цього скористаємося системним викликом
"chdir".
```c
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include "shared.h"
int main() {
char *editor = getenv("EDITOR");
if (editor == NULL)
editor = "vi";
struct stat st = {0};
if (stat(CWD, &st) == -1) {
if (mkdir(CWD, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
err("Can't create directory for files", __LINE__ - 1);
return 1;
}
}
if (chdir(CWD) == -1) {
err("Can't change working directory", __LINE__ - 1);
return 2;
}
if (execlp(editor, editor, (char *)NULL)) {
err("Can't launch editor", __LINE__ - 1);
return 3;
}
return 0;
}
```
==== Розробка програми 2
Друга програма в якості аргументу отримує час, файли відредаговані після якого
необхідно обробити. Перевіримо кількість аргументів, що були передані програмі,
та перетворимо перший з них в потрібний формат, для цього скористаємося функцією
"strptime" стандартної бібліотеки.
```c
if (argc > 2) {
fprintf(stderr, "Error: too many arguments.\n");
return 1;
}
if (argc < 2) {
fprintf(stderr, "Error: too few arguments.\n");
return 1;
}
struct tm tm = {0};
if (strptime(argv[1], "%s", &tm) == NULL) {
fprintf(
stderr,
"Error: please provide time in UNIX timestamp format.\n"
);
return 1;
}
time_t norm_time = mktime(&tm);
```
Для зручного перебору всіх файлів в директорії скористаємося засобами "FTS"
#footnote([
FTS (File Tree Traversal) в POSIX це набір функцій,
що дозволяють послідовно обходити файлову ієрархію (директорії та файли).
FTS надає структурований спосіб для навігації по файловій системі,
отримуючи інформацію про кожен знайдений елемент.
])
, що надає стандарт POSIX.
Створимо об'єкт FTS із коренем в директорії для файлів.
```c
char *paths[] = {CWD, NULL};
FTS *fts = fts_open(paths, FTS_NOCHDIR, NULL);
if (fts == NULL) {
err("Can't initialise FTS", __LINE__ - 1);
return 2;
}
```
Виділимо буфер, в який будемо зберігати вміст файлів.
```c
char *buf = (char *)malloc(1024 * sizeof(char));
```
Та обробимо всі файли, що були змінені після вказаного часу, не заглиблюючись
на інші рівні ієрархії файлової системи.
```c
for (FTSENT *ent = fts_read(fts); ent != NULL; ent = fts_read(fts)) {
switch (ent->fts_info) {
case FTS_F:
if (ent->fts_statp->st_mtime > norm_time) {
...
}
break;
default:
break;
}
}
free(buf);
```
Відкриємо кожен файл та визначимо його кодування (UTF-8, UTF-16LE, UTF-16BE чи CP1251).
```c
FILE *file = fopen(ent->fts_path, "r");
if (file == NULL) {
err("Can't open the file", __LINE__ - 1);
break;
}
size_t bytes_read = fread(buf, 1, 1023, file);
if (ferror(file) != 0) {
err("Can't read the file", __LINE__ - 2);
fclose(file);
break;
}
buf[bytes_read] = '\0';
Encoding enc = get_encoding(buf, bytes_read);
```
В залежності від кодування порахуємо символи нових рядків в файлі. Для UTF-8 та CP1251
новий рядок позначається як один байт "\\n", а для UTF-16LE та UTF-16BE як 2 байти
"\\n\\0" та "\\0\\n" відповідно.
```c
printf("Lines length:");
size_t lines = 0;
size_t line_len = 0;
for (size_t i = (enc == 2 || enc == 1) ? 2 : 0; i < bytes_read; i++) {
line_len++;
int newline_detected = 0;
switch (enc) {
case ENC_UTF16BE:
if (i + 1 < bytes_read && buf[i] == '\0' && buf[i + 1] == '\n') {
newline_detected = 1;
line_len++;
i++;
}
break;
case ENC_UTF16LE:
if (i + 1 < bytes_read && buf[i] == '\n' && buf[i + 1] == '\0') {
newline_detected = 1;
line_len++;
i++;
}
break;
case ENC_CP1251:
case ENC_UTF8:
default:
if (buf[i] == '\n')
newline_detected = 1;
break;
}
if (newline_detected) {
printf(" %zu", line_len);
lines++;
line_len = 0;
}
}
printf("\nTotal lines: %zu\n", lines);
```
Надрукуємо вміст файлу в консоль. Для зручнішого друку якщо текст має будь-яке
кодування крім UTF-8 перетворимо його в UTF-8 скориставшись засобами "iconv"
#footnote([
iconv в POSIX -- це бібліотека, яка надає API для зручного перетворення будь-яких
кодувань символів.
])
, що надає стандарт POSIX.
```c
if (enc != ENC_UTF8)
buf = to_utf8(buf, bytes_read, enc);
printf(NORMAL);
printf("%s\n", buf);
```
==== Розробка програми 3
Третя програма повинна по черзі запустити 2 інші, при цьому зберігши час запуску
першої, та передавши його другій.
```c
time_t t = time(NULL);
char s[256];
strftime(s, 256, "%s", localtime(&t));
```
Як було зазначено раніше, системний виклик "exec" в POSIX сумісних операційних системах
заміняє образ процесу в якому він викликається новим, але в даному випадку нам
необхідно створити новий процес. Для цього скористаємося системним викликом "fork",
який створює точну копію поточного процесу. В скопійованому процесі викличемо "exec",
а в оригінальному дочекаємося завершення виконання копії за допомогою "waitpid".
```c
__pid_t e_pid = fork();
if (e_pid == -1) {
err("Can't fork process for editor", __LINE__ - 1);
return 1;
}
if (!e_pid && execl("build/editor", "build/editor", (char *)NULL)) {
err("Can't launch build/editor", __LINE__ - 1);
return 2;
}
waitpid(e_pid, 0, 0);
```
Повторимо те саме для другої програми.
```c
__pid_t a_pid = fork();
if (a_pid == -1) {
err("Can't fork process for analizer", __LINE__ - 1);
return 3;
}
if (!a_pid && execl("build/analizer", "build/analizer", s, (char *)NULL)) {
err("Can't launch build/analizer", __LINE__ - 1);
return 4;
}
waitpid(a_pid, 0, 0);
```
==== Тестування
Скомпілюємо всі 3 програми та запустимо головну.
Перед нами відкриється редактор у вказаній директорії, створимо в ньому 4 файли з
різним кодуванням.
#figure(image("img/text-files.png"), caption: [Файли з різним кодуванням])
Після закриття редактору можемо побачити наступний результат аналізу файлів.
#figure(image("img/text-result.png"), caption: [Результат аналізу файлів])
=== Завдання 2 -- аналіз поведінки потоків.
Метою цього завдання є дослідження поведінки потоків.
Завдання полягає у створенні 10 потоків, та спостереженні за ними.
В POSIX для керування потоками використовується "pthreads".
Створимо 10 потоків, в кожному з них виведемо в консоль повідомлення про початок
виконання, виконаємо затратну по часу операцію, та виведемо повідомлення про
завершення виконання.
```c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 10
void *thread_function(void *thread_id) {
long tid = (long)thread_id;
printf("Begin\t%ld\n", tid);
for (int i = 0; i < 100000; ++i)
;
printf("End\t%ld\n", tid);
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
long t;
for (t = 0; t < NUM_THREADS; t++)
pthread_create(&threads[t], NULL, thread_function, (void *)t);
void *status;
pthread_join(threads[0], &status);
printf("Main thread completed execution\n");
return 0;
}
```
#figure(image("img/threads-nowait.png"), caption: [Результат виконання])
Можемо побачити, що потоки починають виконання в хаотичному порядку.
Це пояснюється тим, що операційна система передає потоку виконання незважаючи на час,
коли потік було створено. Також можемо побачити, що серед виводу нема повідомлень про
завершення потоків під номерами 7, 8 та 9. Оскільки головна програма чекає на
завершення лише одного потоку, всі потоки, що не встигли завершити своє виконання до
виходу першого автоматично завершуються операційною системою. Щоб це виправити
необхідно або від'єднувати потоки від головної програми, або чекати на завершення
всіх потоків, а не тільки першого.
=== Завдання 3 -- порівняння швидкодії послідовних та паралельних обчислень.
Метою цього завдання є порівняння швидкодії послідовних та паралельних обчислень.
Завдання полягає у створенні програм для паралельного та послідовного множення матриць.
Множення матриць це дуже ресурсо-затратна операція, настільки затратна, що для її
виконання використовується спеціалізоване обладнання -- графічні прискорювачі, які
можуть виконувати тисячі паралельних операцій.
Створимо функції для послідовного та паралельного множення матриць.
```c
void mul_seq(double **a, double **b, double **r, int size) {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
r[i][j] = 0;
for (int k = 0; k < size; k++)
r[i][j] += a[i][k] * b[k][j];
}
}
}
void *mul_par_worker(void *args) {
ThreadArgs *t_args = (ThreadArgs *)args;
double **a = t_args->a;
double **b = t_args->b;
double **r = t_args->r;
int start_row = t_args->start_row;
int end_row = t_args->end_row;
int size = t_args->size;
for (int i = start_row; i < end_row; i++) {
for (int j = 0; j < size; j++) {
r[i][j] = 0;
for (int k = 0; k < size; k++)
r[i][j] += a[i][k] * b[k][j];
}
}
pthread_exit(NULL);
}
void mul_par(double **a, double **b, double **r, int size, int n) {
pthread_t threads[n];
ThreadArgs args[n];
int rows_per_thread = size / n;
int rem_rows = size % n;
int curr_row = 0;
for (int i = 0; i < n; i++) {
args[i].a = a;
args[i].b = b;
args[i].r = r;
args[i].start_row = curr_row;
args[i].end_row = curr_row + rows_per_thread + (i < rem_rows ? 1 : 0);
args[i].size = size;
if (pthread_create(&threads[i], NULL, mul_par_worker, &args[i]) != 0) {
perror("Can't creat thread");
exit(EXIT_FAILURE);
}
curr_row = args[i].end_row;
}
for (int i = 0; i < n; i++) {
if (pthread_join(threads[i], NULL) != 0) {
perror("Can't join thread");
exit(EXIT_FAILURE);
}
}
}
```
Перевіримо швидкодію обох функцій, проведемо паралельні обчислення із використанням
4 потоків.
#figure(image("img/matrix-result.png"), caption: [Результати тестів])
Можемо побачити, що паралельне обчислення із використанням 4 потоків швидше
а послідовне приблизно в $4.4$ рази.
Додаткове збільшення кількості потоків не змінює час виконання в кращу сторону,
а завелика кількість потоків навіть може погіршити результати. Використовувати більше
потоків ніж одночасно підтримує процесор нема сенсу, адже в такому випадку одному
потоку доведеться чекати поки виконується інший, і в такому разі час затрачений
на створення потоку та перемикання контексту процесора може навіть перевищити час,
що було зекономлено за допомогою паралелізації обчислень.
== Висновки
Під час даної лабораторної роботи ми навчились використовувати процеси та потоки при виконанні програм.
#show: appendices_style