add thread deadlock code and Makefile and README

This commit is contained in:
Remzi Arpaci-Dusseau 2020-06-08 04:54:28 -05:00
parent 5888a3aa83
commit 71f570a6d7
12 changed files with 449 additions and 0 deletions

24
threads-bugs/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 common.h common_threads.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

49
threads-bugs/README.md Normal file
View File

@ -0,0 +1,49 @@
# Overview
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.
- `common_threads.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.
The relevant files:
- `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 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 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.
```sh
prompt> make
```
Then you can run a program by simply typing its name:
```sh
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 add?
- `-v`: Verbose flag: prints out a little more about what is going on.
- `-t`: Turns on timing and shows how long everything took.

11
threads-bugs/common.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef __common_h__
#define __common_h__
#include <assert.h>
#include <stdlib.h>
#include <sys/time.h>
#define Malloc(s) ({ void *p = malloc(s); assert(p != NULL); p; })
#define Time_GetSeconds() ({ struct timeval t; int rc = gettimeofday(&t, NULL); assert(rc == 0); (double) t.tv_sec + (double) t.tv_usec/1e6; })
#endif // __common_h__

View File

@ -0,0 +1,37 @@
#ifndef __common_threads_h__
#define __common_threads_h__
#include <pthread.h>
#include <assert.h>
#include <sched.h>
#ifdef __linux__
#include <semaphore.h>
#endif
#define Pthread_create(thread, attr, start_routine, arg) assert(pthread_create(thread, attr, start_routine, arg) == 0);
#define Pthread_join(thread, value_ptr) assert(pthread_join(thread, value_ptr) == 0);
#define Pthread_mutex_init(m, v) assert(pthread_mutex_init(m, v) == 0);
#define Pthread_mutex_lock(m) assert(pthread_mutex_lock(m) == 0);
#define Pthread_mutex_unlock(m) assert(pthread_mutex_unlock(m) == 0);
#define Pthread_cond_init(cond, v) assert(pthread_cond_init(cond, v) == 0);
#define Pthread_cond_signal(cond) assert(pthread_cond_signal(cond) == 0);
#define Pthread_cond_wait(cond, mutex) assert(pthread_cond_wait(cond, mutex) == 0);
#define Mutex_init(m) assert(pthread_mutex_init(m, NULL) == 0);
#define Mutex_lock(m) assert(pthread_mutex_lock(m) == 0);
#define Mutex_unlock(m) assert(pthread_mutex_unlock(m) == 0);
#define Cond_init(cond) assert(pthread_cond_init(cond, NULL) == 0);
#define Cond_signal(cond) assert(pthread_cond_signal(cond) == 0);
#define Cond_wait(cond, mutex) assert(pthread_cond_wait(cond, mutex) == 0);
#ifdef __linux__
#define Sem_init(sem, value) assert(sem_init(sem, 0, value) == 0);
#define Sem_wait(sem) assert(sem_wait(sem) == 0);
#define Sem_post(sem) assert(sem_post(sem) == 0);
#endif // __linux__
#endif // __common_threads_h__

139
threads-bugs/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;
}

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__

View File

@ -0,0 +1,33 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include "common_threads.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"

View File

@ -0,0 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include "common_threads.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,36 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include "common_threads.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"

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__

View File

@ -0,0 +1,32 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include "common_threads.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"

View File

@ -0,0 +1,37 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
#include "common_threads.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"