Exercise

Enhance your understanding by getting hands-on practice ​with this exercise on different concurrency related bugs.

We'll cover the following

This exercise lets you explore some real code that deadlocks (or avoids deadlock). The different versions of code correspond to different approaches to avoiding deadlock in a simplified vector_add() routine. See the previous lesson for details on these programs and their common substrate.


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

Questions

  1. First, let’s make sure you understand how the programs generally work, and some of the key options. Study the code in vector-deadlock.c, as well as in main-common.c and related files. Now, run ./vector-deadlock -n 2 -l 1 -v, which instantiates two threads (-n 2), each of which does one vector add (-l 1), and does so in verbose mode (-v). Make sure you understand the output. How does the output change from run to run?

  2. Now add the -d flag, and change the number of loops(-l)from 1 to higher numbers. What happens? Does the code (always) deadlock?

  3. How does changing the number of threads (-n) change the outcome of the program? Are there any values of -n that ensure no deadlock occurs?

  4. Now examine the code in vector-global-order.c.First, make sure you understand what the code is trying to do; do you understand why the code avoids deadlock? Also, why is there a special case in this vector_add() routine when the source and destination vectors are the same?

  5. Now run the code with the following flags: -t -n 2 -l 100000 -d. How long does the code take to complete? How does the total time change when you increase the number of loops or the number of threads?

  6. What happens if you turn on the parallelism flag (-p)? How much would you expect performance to change when each thread is working on adding different vectors (which is what -p enables) versus working on the same ones?

  7. Now let’s study vector-try-wait.c. First make sure you understand the code. Is the first call to pthread_mutex_trylock() really needed? Now run the code. How fast does it run compared to the global order approach? How does the number of retries, as counted by the code, change as the number of threads increases?

  8. Now let’s look at vector-avoid-hold-and-wait.c. What is the main problem with this approach? How does its performance compare to the other versions, when running both with -p and without it?

  9. Finally, let’s look at vector-nolock.c. This version doesn’t use locks at all; does it provide the exact same semantics like the other versions? Why or why not?

  10. Now compare its performance to the other versions, both when threads are working on the same two vectors (no -p) and when each thread is working on separate vectors (-p). How does this no-lock version perform?

Get hands-on with 1400+ tech skills courses.