mirror of https://github.com/t1meshift/os_labs.git
Add 11th lab
parent
9be2f2302e
commit
f6cf3ad3eb
|
@ -10,11 +10,14 @@
|
|||
- [Лабораторная работа 7](lab7/README.md)
|
||||
- [Лабораторная работа 9](lab9/README.md)
|
||||
- [Лабораторная работа 10](lab10/README.md)
|
||||
- [Лабораторная работа 11](lab11/README.md)
|
||||
|
||||
## Запуск
|
||||
|
||||
Требуется CMake 3.16+ (более ранние версии не проверялись) и компилятор с поддержкой C11.
|
||||
|
||||
Для лабораторной №11 требуются make и gcc.
|
||||
|
||||
```bash
|
||||
./run_lab.sh <lab_directory>
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
ALL = vector-deadlock vector-global-order vector-try-wait vector-avoid-hold-and-wait vector-nolock
|
||||
COMMON = vector-header.h main-common.c main-header.h
|
||||
|
||||
all: $(ALL)
|
||||
|
||||
clean:
|
||||
rm -f $(ALL) *~
|
||||
|
||||
vector-deadlock: vector-deadlock.c $(COMMON)
|
||||
gcc -o vector-deadlock vector-deadlock.c -Wall -pthread -O
|
||||
|
||||
vector-global-order: vector-global-order.c $(COMMON)
|
||||
gcc -o vector-global-order vector-global-order.c -Wall -pthread -O
|
||||
|
||||
vector-try-wait: vector-try-wait.c $(COMMON)
|
||||
gcc -o vector-try-wait vector-try-wait.c -Wall -pthread -O
|
||||
|
||||
vector-avoid-hold-and-wait: vector-avoid-hold-and-wait.c $(COMMON)
|
||||
gcc -o vector-avoid-hold-and-wait vector-avoid-hold-and-wait.c -Wall -pthread -O
|
||||
|
||||
vector-nolock: vector-nolock.c $(COMMON)
|
||||
gcc -o vector-nolock vector-nolock.c -Wall -pthread -O
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Лабораторная работа №11
|
||||
|
||||
## Задание 1
|
||||
|
||||
Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции.
|
||||
Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой
|
||||
программе это маловероятно достижимо.
|
||||
|
||||
При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами.
|
||||
|
||||
В случае, если число потоков = 1, взаимной блокировки не возникнет.
|
||||
|
||||
## Задание 2
|
||||
|
||||
Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок
|
||||
блокировки.
|
||||
|
||||
В случае если адреса совпадают, то это один мьютекс, и для корректной работы программы его надо блокировать 1 раз.
|
||||
|
||||
В случае увеличения числа циклов и потоков, время выполнения растёт.
|
||||
|
||||
В случае включения `-p` время уменьшается, так как разрешается параллелизм.
|
||||
|
||||
## Задание 3
|
||||
|
||||
Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока.
|
||||
|
||||
С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками
|
||||
становится более частым.
|
||||
|
||||
## Задание 4
|
||||
|
||||
Данный подход защищает уязвимое место дедлока, созданием глобального мьютекса, но при этом не даёт различным векторам
|
||||
выполняться параллельно.
|
||||
|
||||
При использовании `-p` время уменьшается.
|
||||
|
||||
## Задание 5
|
||||
|
||||
Указав memory, мы дожидаемся завершения всех операцией с памятью, что своего рода позволяет заменить мьютексы.
|
||||
(https://ru.wikipedia.org/wiki/GCC_Inline_Assembly)
|
||||
|
||||
Сравним время выполнения следующих команд:
|
||||
|
||||
```text
|
||||
./vector-nolock -t -n 2 -l 1000000 -d = 4.08
|
||||
./vector-nolock -t -n 2 -l 1000000 -d -p = 0.65
|
||||
```
|
||||
|
||||
```text
|
||||
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d = 2.98
|
||||
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p = 0.45
|
||||
```
|
||||
|
||||
```text
|
||||
./vector-try-wait -t -n 2 -l 1000000 -d = 1.30
|
||||
./vector-try-wait -t -n 2 -l 1000000 -d -p = 0.18
|
||||
```
|
||||
|
||||
```text
|
||||
./vector-global-order -t -n 2 -l 1000000 -d = 0.69
|
||||
./vector-global-order -t -n 2 -l 1000000 -d -p = 0.19
|
||||
```
|
||||
|
||||
Таким образом видно, что vector-nolock работает медленнее других в любом случае.
|
|
@ -0,0 +1,89 @@
|
|||
|
||||
This homework lets you play around with a number of ways to implement a small,
|
||||
deadlock-free vector object in C. The vector object is quite limited (e.g., it
|
||||
only has add() and init() functions) but is just used to illustrate different
|
||||
approaches to avoiding deadlock.
|
||||
|
||||
Some files that you should pay attention to are as follows. They, in
|
||||
particular, are used by all the variants in this homework.
|
||||
|
||||
- mythreads.h
|
||||
The usual wrappers around many different pthread (and other) library calls,
|
||||
so as to ensure they are not failing silently
|
||||
|
||||
- vector-header.h
|
||||
A simple header for the vector routines, mostly defining a fixed vector size
|
||||
and then a struct that is used per vector (vector_t)
|
||||
|
||||
- main-header.h
|
||||
A number of global variables common to each different program
|
||||
|
||||
- main-common.c
|
||||
Contains the main() routine (with arg parsing) that initializes two vectors,
|
||||
starts some threads to access them (via a worker() routine), and then waits
|
||||
for the many vector_add()'s to complete
|
||||
|
||||
The variants of this homework are found in the following files. Each takes a
|
||||
different approach to dealing with concurrency inside a "vector addition"
|
||||
routine called vector_add(); examine the code in these files to get a sense of
|
||||
what is going on. They all use the files above to make a complete runnable
|
||||
program.
|
||||
|
||||
- vector-deadlock.c
|
||||
This version blithely grabs the locks in a particular order (dst then
|
||||
src). By doing so, it creates an "invitation to deadlock", as one thread
|
||||
might call vector_add(v1, v2) while another concurrently calls
|
||||
vector_add(v2, v1).
|
||||
|
||||
- vector-global-order.c
|
||||
This version of vector_add() grabs the locks in a total order, based on
|
||||
address of the vector.
|
||||
|
||||
- vector-try-wait.c
|
||||
This version of vector_add() uses pthread_mutex_trylock() to attempt to grab
|
||||
locks; when the try fails, the code releases any locks it may hold and goes
|
||||
back to the top and tries it all over again.
|
||||
|
||||
- vector-avoid-hold-and-wait.c
|
||||
This version of vector_add() ensures it can't get stuck in a hold and wait
|
||||
pattern by using a single lock around lock acquisition.
|
||||
|
||||
- vector-nolock.c
|
||||
This version of vector_add() doesn't even use locks; rather, it uses an
|
||||
atomic fetch-and-add to implement the vector_add() routine. Its semantics
|
||||
(as a result) are slightly different.
|
||||
|
||||
Type "make" (and read the Makefile) to build each of five executables.
|
||||
|
||||
prompt> make
|
||||
|
||||
Then you can run a program by simply typing its name:
|
||||
|
||||
prompt> ./vector-deadlock
|
||||
|
||||
Each program takes the same set of arguments (see main-common.c for details):
|
||||
|
||||
-d
|
||||
This flag turns on the ability for threads to deadlock.
|
||||
When you pass -d to the program, every other thread calls vector_add()
|
||||
with the vectors in a different order, e.g., with two threads, and -d enabled,
|
||||
Thread 0 calls vector_add(v1, v2) and Thread 1 calls vector_add(v2, v1)
|
||||
|
||||
-p
|
||||
This flag gives each thread a different set of vectors to call add()
|
||||
upon, instead of just two vectors. Use this to see how things perform
|
||||
when there isn't contention for the same set of vectors.
|
||||
|
||||
-n num_threads
|
||||
Creates some number of threads; you need more than one to deadlock.
|
||||
|
||||
-l loops
|
||||
How many times should each thread call vector_add()?
|
||||
|
||||
-v
|
||||
Verbose flag: prints out a little more about what is going on.
|
||||
|
||||
-t
|
||||
Turns on timing and shows how long everything took.
|
||||
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
|
||||
vector_t v[2 * MAX_THREADS];
|
||||
|
||||
// used to ensure print outs make sense
|
||||
pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void usage(char *prog) {
|
||||
fprintf(stderr, "usage: %s [-d (turn on deadlocking behavior)] [-l loops] [-n num_threads] [-t (do timing)] [-v (for verbose)]\n", prog);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// only called by one thread (not thread-safe)
|
||||
void vector_init(vector_t *v, int value) {
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
v->values[i] = value;
|
||||
}
|
||||
Pthread_mutex_init(&v->lock, NULL);
|
||||
}
|
||||
|
||||
// only called by one thread (not thread-safe)
|
||||
void vector_print(vector_t *v, char *str) {
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
printf("%s[%d] %d\n", str, i, v->values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void print_info(int call_return, int thread_id, int v0, int v1) {
|
||||
if (verbose == 0)
|
||||
return;
|
||||
Pthread_mutex_lock(&print_lock);
|
||||
int j;
|
||||
for (j = 0; j < thread_id; j++) printf(" ");
|
||||
if (call_return)
|
||||
printf("<-add(%d, %d)\n", v0, v1);
|
||||
else
|
||||
printf("->add(%d, %d)\n", v0, v1);
|
||||
Pthread_mutex_unlock(&print_lock);
|
||||
}
|
||||
|
||||
typedef struct __thread_arg_t {
|
||||
int tid;
|
||||
int vector_add_order;
|
||||
int vector_0;
|
||||
int vector_1;
|
||||
} thread_arg_t;
|
||||
|
||||
void *worker(void *arg) {
|
||||
thread_arg_t *args = (thread_arg_t *) arg;
|
||||
int i, v0, v1;
|
||||
for (i = 0; i < loops; i++) {
|
||||
if (args->vector_add_order == 0) {
|
||||
v0 = args->vector_0;
|
||||
v1 = args->vector_1;
|
||||
} else {
|
||||
v0 = args->vector_1;
|
||||
v1 = args->vector_0;
|
||||
}
|
||||
|
||||
print_info(0, args->tid, v0, v1);
|
||||
|
||||
vector_add(&v[v0], &v[v1]);
|
||||
|
||||
print_info(1, args->tid, v0, v1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
opterr = 0;
|
||||
int c;
|
||||
while ((c = getopt (argc, argv, "l:n:vtdp")) != -1) {
|
||||
switch (c) {
|
||||
case 'l':
|
||||
loops = atoi(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
num_threads = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
case 't':
|
||||
do_timing = 1;
|
||||
break;
|
||||
case 'd':
|
||||
cause_deadlock = 1;
|
||||
break;
|
||||
case 'p':
|
||||
enable_parallelism = 1;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
assert(num_threads < MAX_THREADS);
|
||||
|
||||
pthread_t pid[MAX_THREADS];
|
||||
thread_arg_t args[MAX_THREADS];
|
||||
int i;
|
||||
for (i = 0; i < num_threads; i++) {
|
||||
args[i].tid = i;
|
||||
if (enable_parallelism == 0) {
|
||||
args[i].vector_0 = 0;
|
||||
args[i].vector_1 = 1;
|
||||
} else {
|
||||
args[i].vector_0 = i * 2;
|
||||
args[i].vector_1 = i * 2 + 1;
|
||||
}
|
||||
|
||||
if (cause_deadlock && i % 2 == 1)
|
||||
args[i].vector_add_order = 1;
|
||||
else
|
||||
args[i].vector_add_order = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < 2 * MAX_THREADS; i++)
|
||||
vector_init(&v[i], i);
|
||||
|
||||
double t1 = Time_GetSeconds();
|
||||
|
||||
for (i = 0; i < num_threads; i++)
|
||||
Pthread_create(&pid[i], NULL, worker, (void *) &args[i]);
|
||||
for (i = 0; i < num_threads; i++)
|
||||
Pthread_join(pid[i], NULL);
|
||||
|
||||
double t2 = Time_GetSeconds();
|
||||
|
||||
fini();
|
||||
if (do_timing) {
|
||||
printf("Time: %.2f seconds\n", t2 - t1);
|
||||
}
|
||||
|
||||
//vector_print(&v[0], "v1");
|
||||
//vector_print(&v[1], "v2");
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef __main_header_h__
|
||||
#define __main_header_h__
|
||||
|
||||
#define MAX_THREADS (100)
|
||||
|
||||
int loops = 1;
|
||||
int verbose = 0;
|
||||
int num_threads = 2;
|
||||
int do_timing = 0;
|
||||
int cause_deadlock = 0;
|
||||
int enable_parallelism = 0;
|
||||
|
||||
#endif // __main_header_h__
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef __MYTHREADS_h__
|
||||
#define __MYTHREADS_h__
|
||||
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
void *Malloc(size_t size) {
|
||||
void *rc = malloc(size);
|
||||
assert(rc != NULL);
|
||||
return rc;
|
||||
}
|
||||
|
||||
double Time_GetSeconds() {
|
||||
struct timeval t;
|
||||
int rc = gettimeofday(&t, NULL);
|
||||
assert(rc == 0);
|
||||
return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6);
|
||||
}
|
||||
|
||||
void Pthread_mutex_init(pthread_mutex_t *mutex,
|
||||
const pthread_mutexattr_t *attr) {
|
||||
int rc = pthread_mutex_init(mutex, attr);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
|
||||
void Pthread_mutex_lock(pthread_mutex_t *m) {
|
||||
int rc = pthread_mutex_lock(m);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
void Pthread_mutex_unlock(pthread_mutex_t *m) {
|
||||
int rc = pthread_mutex_unlock(m);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
void Pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine)(void*), void *arg) {
|
||||
int rc = pthread_create(thread, attr, start_routine, arg);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
void Pthread_join(pthread_t thread, void **value_ptr) {
|
||||
int rc = pthread_join(thread, value_ptr);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // __MYTHREADS_h__
|
|
@ -0,0 +1,32 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mythreads.h"
|
||||
|
||||
#include "main-header.h"
|
||||
#include "vector-header.h"
|
||||
|
||||
// use this to make lock acquisition ATOMIC
|
||||
pthread_mutex_t global = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void vector_add(vector_t *v_dst, vector_t *v_src) {
|
||||
// put GLOBAL lock around all lock acquisition...
|
||||
Pthread_mutex_lock(&global);
|
||||
Pthread_mutex_lock(&v_dst->lock);
|
||||
Pthread_mutex_lock(&v_src->lock);
|
||||
Pthread_mutex_unlock(&global);
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
v_dst->values[i] = v_dst->values[i] + v_src->values[i];
|
||||
}
|
||||
Pthread_mutex_unlock(&v_dst->lock);
|
||||
Pthread_mutex_unlock(&v_src->lock);
|
||||
}
|
||||
|
||||
void fini() {}
|
||||
|
||||
#include "main-common.c"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mythreads.h"
|
||||
|
||||
#include "main-header.h"
|
||||
#include "vector-header.h"
|
||||
|
||||
void vector_add(vector_t *v_dst, vector_t *v_src) {
|
||||
Pthread_mutex_lock(&v_dst->lock);
|
||||
Pthread_mutex_lock(&v_src->lock);
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
v_dst->values[i] = v_dst->values[i] + v_src->values[i];
|
||||
}
|
||||
Pthread_mutex_unlock(&v_dst->lock);
|
||||
Pthread_mutex_unlock(&v_src->lock);
|
||||
}
|
||||
|
||||
void fini() {}
|
||||
|
||||
#include "main-common.c"
|
|
@ -0,0 +1,35 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mythreads.h"
|
||||
|
||||
#include "main-header.h"
|
||||
#include "vector-header.h"
|
||||
|
||||
void vector_add(vector_t *v_dst, vector_t *v_src) {
|
||||
if (v_dst < v_src) {
|
||||
Pthread_mutex_lock(&v_dst->lock);
|
||||
Pthread_mutex_lock(&v_src->lock);
|
||||
} else if (v_dst > v_src) {
|
||||
Pthread_mutex_lock(&v_src->lock);
|
||||
Pthread_mutex_lock(&v_dst->lock);
|
||||
} else {
|
||||
// special case: src and dst are the same
|
||||
Pthread_mutex_lock(&v_src->lock);
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
v_dst->values[i] = v_dst->values[i] + v_src->values[i];
|
||||
}
|
||||
Pthread_mutex_unlock(&v_src->lock);
|
||||
if (v_dst != v_src)
|
||||
Pthread_mutex_unlock(&v_dst->lock);
|
||||
}
|
||||
|
||||
void fini() {}
|
||||
|
||||
|
||||
#include "main-common.c"
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef __vector_header_h__
|
||||
#define __vector_header_h__
|
||||
|
||||
#define VECTOR_SIZE (100)
|
||||
|
||||
typedef struct __vector {
|
||||
pthread_mutex_t lock;
|
||||
int values[VECTOR_SIZE];
|
||||
} vector_t;
|
||||
|
||||
|
||||
#endif // __vector_header_h__
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mythreads.h"
|
||||
|
||||
#include "main-header.h"
|
||||
#include "vector-header.h"
|
||||
|
||||
// taken from https://en.wikipedia.org/wiki/Fetch-and-add
|
||||
int fetch_and_add(int * variable, int value) {
|
||||
asm volatile("lock; xaddl %%eax, %2;"
|
||||
:"=a" (value)
|
||||
:"a" (value), "m" (*variable)
|
||||
:"memory");
|
||||
return value;
|
||||
}
|
||||
|
||||
void vector_add(vector_t *v_dst, vector_t *v_src) {
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
fetch_and_add(&v_dst->values[i], v_src->values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void fini() {}
|
||||
|
||||
|
||||
#include "main-common.c"
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mythreads.h"
|
||||
|
||||
#include "main-header.h"
|
||||
#include "vector-header.h"
|
||||
|
||||
int retry = 0;
|
||||
|
||||
void vector_add(vector_t *v_dst, vector_t *v_src) {
|
||||
top:
|
||||
if (pthread_mutex_trylock(&v_dst->lock) != 0) {
|
||||
goto top;
|
||||
}
|
||||
if (pthread_mutex_trylock(&v_src->lock) != 0) {
|
||||
retry++;
|
||||
Pthread_mutex_unlock(&v_dst->lock);
|
||||
goto top;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < VECTOR_SIZE; i++) {
|
||||
v_dst->values[i] = v_dst->values[i] + v_src->values[i];
|
||||
}
|
||||
Pthread_mutex_unlock(&v_dst->lock);
|
||||
Pthread_mutex_unlock(&v_src->lock);
|
||||
}
|
||||
|
||||
void fini() {
|
||||
printf("Retries: %d\n", retry);
|
||||
}
|
||||
|
||||
#include "main-common.c"
|
||||
|
Loading…
Reference in New Issue