Add 11th lab

master
Yury Kurlykov 2020-06-11 15:16:58 +10:00
parent 9be2f2302e
commit f6cf3ad3eb
Signed by: t1meshift
GPG Key ID: B133F3167ABF94D8
13 changed files with 556 additions and 0 deletions

View File

@ -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>
```

24
lab11/Makefile Normal file
View File

@ -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

65
lab11/README.md Normal file
View File

@ -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 работает медленнее других в любом случае.

89
lab11/README_FROM_SRC Normal file
View File

@ -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.

139
lab11/main-common.c Normal file
View File

@ -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;
}

13
lab11/main-header.h Normal file
View File

@ -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__

52
lab11/mythreads.h Normal file
View File

@ -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__

View File

@ -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"

24
lab11/vector-deadlock.c Normal file
View File

@ -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"

View File

@ -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"

13
lab11/vector-header.h Normal file
View File

@ -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__

31
lab11/vector-nolock.c Normal file
View File

@ -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"

36
lab11/vector-try-wait.c Normal file
View File

@ -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"