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
-
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 inmain-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? -
Now add the
-d
flag, and change the number of loops(-l
)from 1 to higher numbers. What happens? Does the code (always) deadlock? -
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? -
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 thisvector_add()
routine when the source and destination vectors are the same? -
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? -
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? -
Now let’s study
vector-try-wait.c
. First make sure you understand the code. Is the first call topthread_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? -
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? -
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? -
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.