This is an assignment in which you reason about the use of condition variables. Specifically, you must find bugs in a series of broken implementations of a Thread-safe bounded stack of integers written by a “co-worker.” All code is written in pseudo-code and the specific syntax does not matter.
You should turn in a PDF or plain text README file with your answers in Laulima.
Your co-worker has produced the following code, with the semantic that the application simply terminates if there is a pop on an empty stack or a push on a full stack. These are not great semantics, but before you can voice your discontent your co-worker says: “I have added two functions to check on the empty/full status of the stack, isEmpty() and isFull(), and users can avoid program termination without using any locks themselves by calling these functions to check that the stack isn’t full/empty before pushing/popping”.
Explain why your co-worker’s statement above is wrong by giving a simple sequence of events when more than one thread manipulates the stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Stack {
int size;
int items[SIZE];
Lock mutex;
}
void push(Stack stack, int value) {
lock(stack.mutex);
if (stack.size == SIZE) {
print "Stack full";
exit;
}
stack.items[stack.size] = value;
stack.size ++;
unlock(stack.mutex);
}
int pop(Stack stack) {
lock(stack.mutex);
if (stack.size == 0) {
print "Stack empty";
exit;
}
int value = stack.items[stack.size - 1];
stack.size --;
unlock(stack.mutex);
return value;
}
int isEmpty(Stack stack) {
return (stack.size == 0);
}
int isFull(Stack stack) {
return (stack.size == SIZE);
}
Convinced by your argument, your co-worker goes back to the drawing board and produces a new implementation. The new semantic is that push() and pop() return a success code, a failure code, or an integer value. However, the code he shows you below is broken. Explain why and refer to problematic line numbers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Stack {
int size;
int items[SIZE];
Lock mutex;
}
int push(Stack stack, int value) {
lock(stack.mutex);
if (stack.size == SIZE) {
return STACK_ERROR;
}
stack.items[stack.size] = value;
stack.size ++;
unlock(stack.mutex);
return STACK_SUCCESS;
}
int pop(Stack stack) {
lock(stack.mutex);
if (stack.size == 0) {
return STACK_ERROR;
}
int value = stack.items[stack.size - 1];
stack.size --;
unlock(stack.mutex);
return value;
}
You’re tired of this “do everything with locks” vibe, and of the lame semantics. You have taken ICS432 and thus proceed to teach your co-worker about condition variables, realizing in the process that teaching concurrency is harder than it looks. Your co-worker then produces the code below.
Before you even being to look at the code, from the basement comes Frank with his bushy beard, his FreeBSD t-shirt from 1993, and his rainbow suspenders. He looks at the code for .1 sec from 10 feet away and says: “It’s broken! I’ve been doing threads since the 80’s, before it was cool.” After rambling for 10 minutes about some concurrency research he did at Xerox Park, he condescends to show everybody 3 big problems with the code.
What are these problems? (the “same problem” in pop() and push() counts only as one).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Stack {
int size;
int items[SIZE];
Lock mutex;
Cond cond;
}
void push(Stack stack, int value) {
if (stack.size == SIZE) {
lock(stack.mutex);
wait(stack.cond, stack.mutex);
}
stack.items[stack.size] = value;
stack.size ++;
unlock(stack.mutex);
return;
}
int pop(Stack stack) {
if (stack.size == 0) {
lock(stack.mutex);
wait(stack.cond, stack.mutex);
}
int value = stack.items[stack.size - 1];
stack.size --;
unlock(stack.mutex);
return value;
}