CSE 120: Homework #2
Spring 2023
Due: Tuesday, May 2 at 11:59pm
For the homework questions below, if you believe that you cannot
answer a question without making some assumptions, state those
assumptions in your answer.
- The Intel x86 instruction set architecture provides an atomic
instruction called XCHG for implementing synchronization primitives.
(If you are
curious, this
reference page shows the full syntax and semantics of the
instruction.) Semantically, XCHG works as follows (although keep in
mind it is executed atomically):
void XCHG (bool *X, bool *Y) {
bool tmp = *X;
*X = *Y;
*Y = tmp;
}
Show how XCHG can be used instead of test-and-set to implement the
acquire() and release() functions of the spinlock data structure described
in the "Synchronization" lecture.
struct lock {
...
}
void acquire (struct lock *) {
...
}
void release (struct lock *) {
...
}
- One of the goals of this question is to give you practice with
context switching and thread queue manipulation in Nachos.
Consider the following test program for an implementation
of KThread.join in Nachos. It begins when
the main Nachos thread calls KThread.selfTest.
You do not need to know the details of how join is implemented. All
you need to know is that when a parent thread calls join on
a child thread, the parent does one of two things: (1) if the child
is still running, the parent blocks until the child finishes (at
which point the parent is placed on the ready queue); (2) if the
child has finished, the parent continues to execute without
blocking. Assume join uses a wait queue of some kind in
its implementation.
private static class A implements Runnable {
A () {}
public void run () {
KThread t2 = new KThread (new B()).setName ("B");
System.out.println ("foo");
t2.fork ();
System.out.println ("far");
t2.join ();
System.out.println ("fum");
}
}
private static class B implements Runnable {
B () {}
public void run () {
System.out.println ("fie");
}
}
public static void selfTest() {
KThread t1 = new KThread (new A()).setName ("A");
System.out.println ("fee");
t1.fork ();
System.out.println ("foe");
t1.join ();
System.out.println ("fun");
}
Assume that the scheduler runs threads in FIFO order with
non-preemptive scheduling (no preemptive time-slicing) on a uniprocessor, and threads
are placed on wait queues in FIFO order. Trace the execution of
this program until it returns from selfTest and (a) write the
sequence of context switches that occurred up this point, (b) write
the output of the program, and (c) list the queues that the threads
are on, and their relative order if more than one thread is on a
queue.
a. Context switches: main →
b. Output:
c. Thread queues when selfTest returns:
currentThread:
readyQueue:
join wait queue:
[Hint: First try the problem by following the code manually,
keeping track of which queues threads are on using paper. Then,
once you have implemented join, try adding the code as a
test in KThread.java and running it to check your answer.]
- A common pattern in parallel scientific programs is to
have a set of threads do a computation in a sequence of phases.
In each phase i, all threads must finish phase i
before any thread starts computing phase i+1. One way
to accomplish this is with barrier synchronization. At the end
of each phase, each thread executes Barrier::Done(n), where
n is the number of threads in the computation. A call to
Barrier::Done blocks until all of the n threads have
called Barrier::Done. Then, all threads proceed. You may
assume that the process allocates a new Barrier for each iteration,
and that all threads of the program will call Done with the same
value.
Implement Barrier using lock and condition
variable (Mesa style signal).
class Barrier {
...private variables...
void Done (int n) {
...
}
...
}
-
Microsoft .NET provides a synchronization primitive called a
CountdownEvent. Programs use CountdownEvent to synchronize on the
completion of many threads (similar to CountDownLatch in Java). A
CountdownEvent is initialized with a count, and a
CountdownEvent can be in two states, nonsignalled and
signalled. Threads use a CountdownEvent in the nonsignalled
state to Wait (block) until the internal count reaches zero.
When the internal count of a CountdownEvent reaches zero, the
CountdownEvent transitions to the signalled state and wakes up
(unblocks) all waiting threads. Once a CountdownEvent has
transitioned from nonsignalled to signalled, the CountdownEvent
remains in the signalled state. In the nonsignalled state, at any
time a thread may call the Decrement operation to decrease the
count and Increment to increase the count. In the signalled
state, Wait, Decrement, and Increment have no
effect and return immediately.
Use pseudo-code to implement a thread-safe CountdownEvent using locks
and condition variables by implementing the following methods:
class CountdownEvent {
...private variables...
CountdownEvent (int count) { ... }
void Increment () { ... }
void Decrement () { ... }
void Wait () { ... }
}
Notes:
- The CountdownEvent constructor takes an integer
count as input and initializes the CountdownEvent counter
with count. Positive values of count cause the
CountdownEvent to be constructed in the nonsignalled state. Other
values of count will construct it in the signalled state.
- Increment increments the internal counter.
- Decrement decrements the internal counter. If the counter
reaches zero, the CountdownEvent transitions to the signalled state
and unblocks any waiting threads.
- Wait blocks the calling thread if the CountdownEvent is
in the nonsignalled state, and otherwise returns.
- Each of these methods is relatively short.
- Annabelle, Bertrand, Chloe and Dag are working on their term
papers in CSE 120, which is a 10,000 word essay on My All-Time
Favorite Race Conditions. To help them work on their papers, they
have one dictionary, two copies of Roget's Thesaurus, and two coffee
cups.
-
Annabelle needs to use the dictionary and a thesaurus
to write her paper;
-
Bertrand needs a thesaurus and a coffee cup to write
his paper;
-
Chloe needs a dictionary and a thesaurus to write
her paper;
-
Dag needs two coffee cups to write his paper (he
likes to have a cup of regular and a cup of decaf at the same time to keep
himself in balance).
Consider the following state:
-
Annabelle has a thesaurus and needs the dictionary.
-
Bertrand has a thesaurus and a coffee cup.
-
Chloe has the dictionary and needs a thesaurus.
-
Dag has a coffee cup and needs another coffee cup.
Is the system deadlocked in this state? Explain using
a resource allocation graph as a reference.