add thread deadlock code and Makefile and README
This commit is contained in:
parent
5888a3aa83
commit
71f570a6d7
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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__
|
|
@ -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__
|
|
@ -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,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"
|
||||
|
|
@ -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"
|
|
@ -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"
|
||||
|
|
@ -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,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"
|
||||
|
|
@ -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"
|
||||
|
Loading…
Reference in New Issue