diff --git a/README.md b/README.md index 2577ab2..9a22440 100644 --- a/README.md +++ b/README.md @@ -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 ``` \ No newline at end of file diff --git a/lab11/Makefile b/lab11/Makefile new file mode 100644 index 0000000..fae2efe --- /dev/null +++ b/lab11/Makefile @@ -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 + diff --git a/lab11/README.md b/lab11/README.md new file mode 100644 index 0000000..0aa20b5 --- /dev/null +++ b/lab11/README.md @@ -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 работает медленнее других в любом случае. diff --git a/lab11/README_FROM_SRC b/lab11/README_FROM_SRC new file mode 100644 index 0000000..96e25e9 --- /dev/null +++ b/lab11/README_FROM_SRC @@ -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. + + diff --git a/lab11/main-common.c b/lab11/main-common.c new file mode 100644 index 0000000..eb260c7 --- /dev/null +++ b/lab11/main-common.c @@ -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; +} diff --git a/lab11/main-header.h b/lab11/main-header.h new file mode 100644 index 0000000..73e6fe5 --- /dev/null +++ b/lab11/main-header.h @@ -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__ diff --git a/lab11/mythreads.h b/lab11/mythreads.h new file mode 100644 index 0000000..f4cfd69 --- /dev/null +++ b/lab11/mythreads.h @@ -0,0 +1,52 @@ +#ifndef __MYTHREADS_h__ +#define __MYTHREADS_h__ + +#include +#include +#include +#include + +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__ diff --git a/lab11/vector-avoid-hold-and-wait.c b/lab11/vector-avoid-hold-and-wait.c new file mode 100644 index 0000000..349fc0d --- /dev/null +++ b/lab11/vector-avoid-hold-and-wait.c @@ -0,0 +1,32 @@ +#include +#include +#include + +#include + +#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" + diff --git a/lab11/vector-deadlock.c b/lab11/vector-deadlock.c new file mode 100644 index 0000000..911f0a4 --- /dev/null +++ b/lab11/vector-deadlock.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +#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" diff --git a/lab11/vector-global-order.c b/lab11/vector-global-order.c new file mode 100644 index 0000000..b5953d3 --- /dev/null +++ b/lab11/vector-global-order.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#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" + diff --git a/lab11/vector-header.h b/lab11/vector-header.h new file mode 100644 index 0000000..c803ce8 --- /dev/null +++ b/lab11/vector-header.h @@ -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__ + diff --git a/lab11/vector-nolock.c b/lab11/vector-nolock.c new file mode 100644 index 0000000..92323cf --- /dev/null +++ b/lab11/vector-nolock.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +#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" + diff --git a/lab11/vector-try-wait.c b/lab11/vector-try-wait.c new file mode 100644 index 0000000..904f490 --- /dev/null +++ b/lab11/vector-try-wait.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#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" +