
modern-multithreading-c-java
.pdfREFERENCES |
165 |
Hart, Johnson M. (2000a). Win32 System Programming: A Windows 2000 Application Developer’s Guide, 2nd ed. Reading, MA: Addison-Wesley.
Hart, Johnson M. (2000b). Online supplement to Win32 System Programming: A Windows 2000 Application Developer’s Guide, 2nd ed. Reading, MA: Addison-Wesley, http://world.std.com/ jmhart/csmutx.htm.
Havelund, K., and T. Pressburger (2000). Model checking Java programs using Java PathFinder. International Journal on Software Tools for Technology Transfer, Vol. 2, No. 4 (April), pp. 366–381.
Howard, David M. (2000). Using predicates with Win32 threads. C/C++ Users Journal, May, pp. 18–30.
Hwang, Gwan-Hwan, Kuo-Chung Tai, and Ting-Lu Huang (1995). Reachability testing: an approach to testing concurrent software. International Journal of Software Engineering and Knowledge Engineering, Vol. 5, No. 4, pp. 493–510.
Khamsi, Sarir (2003). A class for handling shared memory under Win32. C/C++ Users Journal, May, p. 34.
Kleber, Jeff (2000). Thread-safe |
access |
to collections. C/C++ Users Journal, |
May, |
pp. 36–39. |
|
|
|
LaPlante, John (2003). Efficient |
thread |
coordination. C/C++ Users Journal, |
May, |
pp. 6–19. |
|
|
|
Lowy, Juval (2000). Making primitive objects thread safe. C/C++ Users Journal, March, pp. 85–86.
Manley, Kevin (1999). Improving performance with thread-private heaps. C/C++ Users Journal, September, pp. 50–62.
Meyers, Scott, and Andrei Alexandrescu (2004a). C++ and the perils of double-checked locking: Part I. Dr. Dobb’s Journal, August, pp. 46–49.
Meyers, Scott, and Andrei Alexandrescu (2004b). C++ and the perils of double-checked locking: Part II. Dr. Dobb’s Journal, September, pp. 57–61.
Nonaka, Yusuka, Kazuo Ushijima, Hibiki Serizawa, Shigeru Murata, and Jingde Cheng (2001). A run-time deadlock detector for concurrent Java programs. Proc. 8th AsiaPacific Software Engineering Conference (APSEC’01), pp. 45–52.
Petzold, Charles (1998). Programming Windows, 5th ed. Redmond, WA: Microsoft Press.
Pugh, William (1999). Fixing the Java memory model. Proc. ACM Java Grande Conference, pp. 89–98.
Ringle, Jon (1999a). Singleton creation: the thread-safe way. C/C++ Users Journal, October, pp. 43–49.
Ringle, Jon (1999b). We Have Mail: letter. C/C++ Users Journal, December.
Savage, S., M. Burrows, G. Nelson, P. Sobalvarro, and T. E. Anderson (1997). Eraser: a dynamic data race detector for multithreaded programs. ACM Transactions on Computer Systems, Vol. 15, No. 4 (April), pp. 391–411.
Silberschatz, Abraham, Peter Baer Galvin, and Greg Gagne (2001). Operating System Concepts, 6th ed. New York: Wiley.
Sun Microsystems (2002). Java virtual machines. http://java.sun.com/j2se/1.4.2/docs/ guide/vm.
Tai, K. C., and R. H. Carver (1996). VP: a new operation for semaphores. ACM Operating Systems Review, Vol. 30, No. 3, pp. 5–11.

166 |
SEMAPHORES AND LOCKS |
EXERCISES
3.1.Can the bounded-buffer solution in Section 3.5 be used with multiple producers and consumers? If not, modify it to produce a correct solution.
3.2.Section 3.4 defined strong and weak semaphores. Assume that semaphore mutex is initialized to 1 and that each thread executes:
mutex.P();
/* critical section */ mutex.V();
(a)Suppose that there are two threads and mutex is a weak semaphore. Is the critical section problem solved?
(b)Suppose there are three threads and mutex is a strong semaphore. Is the critical section problem solved?
(c)Is class countingSemaphore in Listing 3.15 a strong or weak semaphore?
3.3.Consider the semaphore-based solution to strategy R<W.2 in Section 3.5.4.
(a)Is it possible that a writer waits on readers w que.P()? Explain.
(b)Is it possible that a reader waits on writers que.P()? Explain.
3.4.The InterlockedExchange function was described in Section 2.3. It has two parameters and behaves like the following atomic function:
LONG InterlockedExchange (LONG* target, LONG newValue) { LONG temp = *target; *target = newValue; return temp;
}
The following is an implementation of a countingSemaphore using InterlockedExchange():
class countingSemaphore { |
|
private: |
|
volatile int permits; |
|
volatile LONG mutex; |
// provides mutual exclusion for permits |
volatile LONG block; |
// used to delay threads waiting on permits |
public:
countingSemaphore(int init) : permits(init), mutex(0), block(1) {} voidP() {
while (InterlockedExchange(const_cast<LONG*>(&mutex),true)) {Sleep(0);}
permits = permits - 1 ; if (permits < 0) {
mutex = false;

EXERCISES |
167 |
while (InterlockedExchange(const_cast<LONG*>(&block),true)) {Sleep(0);}
}
else
mutex = false;
}
void V() {
while (InterlockedExchange(const_cast<LONG*>(&mutex),true)) {Sleep(0);};
permits = permits + 1 ; if (permits <= 0) {
while (!block) {Sleep(0);}; block = false;
}
mutex = false;
}
};
Statements
while (InterlockedExchange(const_cast<LONG*>(&mutex),true)) {Sleep(0);};
and
mutex = false;
are used to enforce mutual exclusion for permits. In the P() operation, the statement
while (InterlockedExchange(const_cast<LONG*>(&block),true)) {Sleep(0);}
is used to delay threads that are waiting to be awakened by V() operations. In the V() operation, the statement
while (!block) {Sleep(0);};
is used to make sure that block is set to false only when it is true. The initial value of block is true. Block is set to false in order to awaken a delayed thread. After block is set to false, it remains false until a delayed thread executes InterlockedExchange(const cast<LONG > (&block),true). (Then the delayed thread becomes awakened.) If block is false when V() is executed, the previous V() operation has not yet awakened a delayed thread.
(a)Show that the deletion of while (!block) {Sleep(0);} in V() can create an error.

168 |
SEMAPHORES AND LOCKS |
(b) Can the implementation of V() be replaced by the following?
public void V(int s) {
while (InterlockedExchange(const_cast<LONG*>(&mutex),true)) {Sleep(0);};
permits = permits + 1; if (permits <= 0) {
mutex = false;
while (!block) {Sleep 0;}; block = false;
}
else
mutex = false;
}
3.5. Section 3.5.5 shows how to use binary semaphores to implement P() and V() operations for a countingSemaphore class. Suppose that binary semaphores are redefined so that a V() operation never blocks the calling thread. That is, if the value of a binary semaphore is 1 when a V() operation is called, the value is not modified and the calling thread simply completes the operations and returns.
(a)Are the implementations of P() and V() still correct if these new binary semaphores are used? Explain.
(b)If not, suppose that we replace the statements
mutex.V();
delayQ.P();
with the single statement
delayQ.VP(mutex);
Is this new implementation correct? Explain.
3.6.In Section 3.5.4 a semaphore-based solution to strategy R<W.2 for the readers and writers problem is given.
(a)Below is a revised version of the read operation, in which semaphore mutex r is deleted and semaphore readers w que is used to provide mutual exclusion for accessing activeReaders and for synchronization between readers and writers. Does the revised solution still implement strategy R<W.2? Explain.
Read() {
readers_w_que.P(); // readers may be blocked by a writer ++activeReaderCount;
if (activeReaderCount = 1) writer_que.P()
EXERCISES |
169 |
readers_w_que.V(); /* read x */ readers_w_que.P(); --activeReaderCount;
if (activeReaderCount = 0) writer_que.V(); readers_w_que.V();
}
(b)Below is a revised version of the read operation in which the last two statements have been reversed. Does the revised solution still implement strategy R<W.2? Explain.
Read() { |
|
readers_w_que.P(); |
|
mutex_r.lock(); |
|
++activeReaders; |
|
if (activeReaders == 1) writers_que.P(); |
|
mutex_r.unlock(); |
|
readers_w_que.V(); |
|
/* read x */ |
|
mutex_r.lock(); |
|
--activeReaders; |
|
mutex_r.V(); |
// ***** these two statements |
if (activeReaders == 0) writers_que.V(); |
// ***** were reversed |
} |
|
3.7.The unisex bathroom [Andrews 2000]. This problem is similar to the readers and writers problem. Suppose that there is one bathroom in your office. It can be used by both men and women, but not by both at the same time.
(a)Develop a semaphore solution that allows any number of men or any number of women (but not both) in the bathroom at the same time. Your solution should ensure the required exclusion and avoid deadlock, but it need not be fair (i.e., some people may never get to use the bathroom).
(b)Modify your solution to part (a) to ensure that at most four people are in the bathroom at the same time.
(c)Develop a semaphore solution that ensures fairness:
žAll men and women eventually get to use the bathroom.
žWhen the last man in the bathroom exits, all women that are currently waiting are allowed to enter the bathroom.
žWhen the last woman in the bathroom exits, all men that are currently waiting are allowed to enter the bathroom.
žIf men are in the bathroom, another man cannot enter the bathroom if women are waiting.
žIf women are in the bathroom, another women cannot enter the bathroom if men are waiting.
170 |
SEMAPHORES AND LOCKS |
3.8.Write a semaphore implementation of an Event object that is similar to the Event object in Win32. Event objects have operations block() and set(). A call to block() always blocks the caller. A call to set() awakens every thread that has called block() since the last time set() was called. Use P() and V() (but no VP()) operations on semaphores. Declare and initialize any variables and semaphores that you use.
3.9.Are the following busy-waiting definitions of operations P() and V() for a counting semaphore s correct?
P(): permits = permits-1; while (permits < 0) {;} V(): permits = permits + 1;
3.10.Solution 4 of the dining philosophers problem allows a philosopher to starve. Show an execution sequence in which a philosopher starves.
3.11.Below is a proposed implementation of operations P() and V() in class BinarySemaphore. Is this implementation correct? Explain.
public synchronized void P() {
while (value==0) try { wait(); } catch ( InterruptedException e) { } value=0;
notify();
}
public synchronized void V() {
while (value==1) try { wait(); }catch ( InterruptedException e) { } value=1;
notify();
}
3.12.Many problems require iterative solutions. In these solutions, n threads are created at the beginning of execution. Each thread performs some subtask on each iteration. All n threads synchronize at the end of each iteration:
Worker Thread i { while (true) {
perform thread i’s task;
wait for all n threads to complete;
}
}
This is called barrier synchronization, because the wait at the end of each iteration represents a barrier that all threads have to arrive at before any are allowed to pass. Notice that the same barrier is reused at the end of each iteration.
Use semaphores to implement a reusable barrier for n threads. A template for class Barrier is given below.
EXERCISES |
171 |
class Barrier { |
|
private int count = 0; |
// count of waiting threads |
private int n = 0; |
// number of threads |
private binarySemaphore mutex(1); // provides mutual exclusion |
|
private binarySemaphore go(0); |
// a queue for threads to wait in until |
|
// they are permitted to go |
public Barrier (int n) {this.n = n); |
|
public void waitB() { |
// call b.waitB() to wait on Barrier b |
/* implement this method */ |
|
} |
|
} |
|
Complete the template. You can assume that the semaphores are FCFS semaphores.
3.13.The bear and the honeybees [Andrews 2000]. There are n bees and one bear who share a pot of honey. The pot is initially empty; its capacity is H portions of honey, where H ¡= n.
žThe bee threads repeatedly put one portion of honey in the pot (i.e., pot++); the bee who fills the pot awakens the bear. When the pot is full, bees must wait until the bear eats all the honey.
žThe bear thread sleeps until the pot is full (i.e., pot == H), then eats all the honey and goes back to sleep.
The pot is a shared resource (implemented as a variable), so at most one bee or the bear can access it at a time. Write implementations of bee and bear threads using P() and V() operations on semaphores.
3.14.Message exchange [Carr et al. 2001]. There are two groups of threads. Threads from group A wish to exchange integer messages with threads in group B. Group A threads execute method exchangeWithB() while group B threads execute method exchangeWithA():
void exchangeWithB() { |
void exchangeWithA { |
while (true) { |
while (true) { |
msgA = ‘‘A message’’; |
msgB = ‘‘B message’’; |
/* exchange message */ |
/* exchange message */ |
// now msgA is ‘‘B message’’ |
// now msgB is ‘‘A message’’ |
} |
} |
} |
} |
There are two constraints:
žOnce a thread TA from group A makes a message available, TA can continue only if it receives a message from a thread TB in group B that has successfully retrieved TA’s message. Similarly, thread TB can continue only if it receives a massage from TA rather than from some other thread in group A.
172 |
SEMAPHORES AND LOCKS |
žOnce a thread TA from group A makes its message available, another thread in group A should not be allowed to overwrite TA’s message before the message is retrieved by a thread in group B.
For each solution below, state whether the solution is correct or incorrect. If the solution is incorrect, describe a scenario that illustrates the error.
(a)countingSemaphore A(0), B(0);77 int bufferA, bufferB;
void exchangeWithB() { |
void exchangeWithA() { |
int msgA; |
int msgB; |
while (true) { |
while (true) { |
// create the messages that will be exchanged |
|
msgA = ...; |
msgB = ...; |
// signal the other thread that you are ready |
|
B.V(); |
A.V(); |
// wait for signal from the other thread |
|
A.P(); |
B.P(); |
// make a copy of message for the other thread |
|
bufferA = msgA; |
bufferB = msgB; |
// swap copies; now msgB contains A’s message |
|
msgA = bufferB; |
msgB = bufferA; |
// and msgA contains B’s message |
|
...; |
...; |
} |
} |
} |
} |
(b) binarySemaphore mutex(1); countingSemaphore A(0), B(0); int bufferA, bufferB;
void exchangeWithB() { |
void exchangeWithA() { |
int msgA; |
int msgB; |
while (true) { |
while (true) { |
// create the messages that will be exchanged |
|
msgA = ...; |
msgB = ...; |
// signal the other thread you are ready |
|
B.V(); |
A.V(); |
// wait forsignal from the other thread |
|
A.P(); |
B.P(); |
// critical section start |
|
mutex.P(); |
mutex.P(); |
// make copy for the other thread |
|
bufferA = msgA; |
bufferB = msgB; |
// critical section end |
|
mutex.V(); |
mutex.V(); |
EXERCISES |
173 |
// signal ready to swap
B.V(); |
A.V(); |
// wait for signal from the other thread |
|
A.P(); |
B.P(); |
// critical section start |
|
mutex.P(); |
mutex.P(); |
// swap copies |
|
msgA = bufferB; |
msgB = bufferA; |
// critical section end |
|
mutex.V(); |
mutex.V(); |
// now msgB contains A’s message |
|
...; |
...; |
// and msgA contains B’s message |
|
} |
} |
} |
} |
(c) binarySemaphore Aready(1), Bready(1); countingSemaphore Adone(0), Bdone(0);
int bufferA, bufferB;
void exchangeWithB() { |
void exchangeWithA() { |
int msgA; |
int msgB; |
while (true) { |
while (true) { |
// create the messages that will be exchanged |
|
msgA = ...; |
msgB = ...; |
// critical section start |
|
Aready.P(); |
Bready.P(); |
// make a copy of message for the other thread |
|
bufferA = msgA; |
bufferB = msgB; |
// signal ready to swap |
|
Adone.V(); |
Bdone.V(); |
// wait for signal from the other thread |
|
Bdone.P(); |
Adone.P(); |
// swap copies |
|
msgA = bufferB; |
msgB = bufferA; |
// critical section end |
|
Aready.V(); |
Bready.V(); |
// now msgB contains A’s message |
|
...; |
...; |
// and msgA contains B’s message |
|
} |
} |
} |
} |
(d) binarySemaphore Aready(1), Bready(1); countingSemaphore Adone(0), Bdone(0);
int bufferA, bufferB;
174 |
SEMAPHORES AND LOCKS |
void exchangeWithB() { |
void exchangeWithA() { |
int msgA; |
int msgB; |
while (true) { |
while (true) { |
// create the messages that will be exchanged |
|
msgA = ...; |
msgB = ...; |
// critical section start |
|
Bready.P(); |
Aready.P(); |
// make copy for the other thread |
|
bufferA = msgA; |
bufferB = msgB; |
// signal ready to swap |
|
Adone.V(); |
Bdone.V(); |
// wait for signal from the other thread |
|
Bdone.P(); |
Adone.P(); |
// swap copies |
|
msgA = bufferB; |
msgB = bufferA; |
// critical section end and signal other ...; |
|
Aready.V(); |
Bready.V(); |
// now msgB contains A’s message |
|
...; |
...; |
// and msgA contains B’s message |
|
} |
} |
} |
} |
(e)binarySemaphore Amutex(1), Bmutex(1); binarySemaphore notFullA(1), notFullB(1), notEmptyA(1),
notEmptyB(0);
void exchangeWithB() { |
void exchangeWithA() { |
int msgA; |
int msgB; |
while (true) { |
while (true) { |
// create the messages that will be exchanged |
|
msgA = ...; |
msgB = ...; |
// critical section start |
|
Amutex.P(); |
Bmutex.P(); |
// wait for previous swap complete |
|
notFullA.P(); |
notFullB.P(); |
// make copy |
|
bufferA = msgA; |
bufferB = msgB; |
// signal the other thread copy made |
|
notEmptyA.V(); |
notEmptyB.V(); |
// wait for copy to be made |
|
notEmptyB.P(); |
notEmptyA.P(); |
// swap |
|
msgA = bufferB; |
msgB = bufferA; |
// signal swap complete