modern-multithreading-c-java
.pdfEXERCISES |
305 |
5.2.In Exercise 3.11 you were asked to write a semaphore implementation of operation waitB() in class Barrier. Here you are asked to write an implementation using send() and receive() operations and a selective wait statement. Threads call B.waitB() to wait at Barrier B:
class Barrier extends Thread { public Barrier(int n) {this.n = n;}
public void waitB() {arrive.send(); block.send(); }
public void run() { /* receive calls to ports arrive and block */ } private selectablePort arrive = new selectablePort();
private selectablePort block = new selectablePort();
}
Inside method waitB(), a call to arrive.send() signals a thread’s arrival at the barrier. The call to block.send() blocks the threads until all n threads have arrived at the barrier. Assume that the message queues for ports are FCFS. Implement method run() using a selective wait statement.
5.3.In Exercise 4.8 you were asked to write a monitor implementation of the unisex bathroom problem. Here you are asked to write an implementation using send() and receive() operations on selectablePorts.
(a)Implement the strategy in Exercise 4.8, part (a).
(b)Implement the strategy in Exercise 4.8, part (b).
Your implementation should complete the run() method below. Provide expressions for the guards, and implement the body for each of the four cases. Remember that a call to E.count() returns the number of messages that are waiting to be received for port E.
public void run() { try {
selectiveWait select = new selectiveWait();
select.add(menEnter); |
// alternative 1 |
select.add(menExit); |
// alternative 2 |
select.add(womenEnter); |
// alternative 3 |
select.add(womenExit); |
// alternative 4 |
while(true) { |
|
menEnter.guard(/* TBD */); |
|
menExit.guard(/* TBD */); |
|
womenEnter.guard(/* TBD */); womenExit.guard(/* TBD */); int choice = select.choose(); switch (choice) {
case 1: menEnter.receive(); /* ...; TBD; ...; */ break; case 2: menExit.receive(); /* ...; TBD; ...;*/ break; case 3: womenEnter.receive(); /* ...; TBD; ...; */ break;
306 |
MESSAGE PASSING |
case 4: womenExit.receive(); /* ...; TBD; ...; */ break;
}
}
} catch (InterruptedException e) {}
}
5.4.The bear and the honeybees (see Exercise 3.13). Solve this problem using selectablePorts and a selective wait statement. The bear calls port eat to eat the honey. The bees call port fillPot. Your implementation should complete the run() method below. Provide expressions for the guards, and implement the body for both cases.
public void run() { try {
selectiveWait select = new selectiveWait();
select.add(eat); |
// alternative 1 |
select.add(fillPot); |
// alternative 2 |
while(true) { |
|
eat.guard(/* TBD */); fillPot.guard(/* TBD */); int choice = select.choose(); switch (choice) {
case 1: eat.receive(); /* ...; TBD; ...; */ break; case 2: fillPot.receive(); /* ...; TBD; ...;*/ break;
}
}
} catch (InterruptedException e) {}
}
5.5.This exercise compares if-else statements and selective wait statements.
(a)Consider the following if-else statement:
if (condition) // this can be any condition (e.g., A > 0) E1.receive();
else E2.receive();
Show how to simulate this if-else statement by using a selectiveWait. If you cannot write an exact simulation, explain why.
(b) Consider the following if-else statement:
if (condition) |
// this can be any condition (e.g., A > 0) |
E1.receive();
EXERCISES |
307 |
Show how to simulate this if-statement by using a selectiveWait. If you cannot write an exact simulation, explain why.
5.6.An else alternative of a selective wait is selected if (1) there are no other open alternatives (i.e., no alternatives with a true guard), or (2) at least one open alternative exists, but no messages are waiting for the open alternatives. Assume that the other alternatives are accept alternatives for ports E1, E2, . . . , En. Is (2) equivalent to the following condition? If not, try to correct this condition:
(!E1.guard() || E1.Count()==0) && (!E2.guard() || E2.Count()==0) && ...
&&(!En.guard() || En.Count()==0)
5.7.An implementation of readers and writers strategy R>W.1 is given below. Modify this implementation of R>W.1 to implement the following three strategies:
(a)R<W.1: Many readers or one writer, with writers having a higher priority.
(b)R<W.2: Same as R<W.1, except that when a writer requests to write, if no writer is writing or waiting, it waits until all readers that issued earlier requests have finished reading.
(c)R<W.3: Same as R<W.1 except that at the end of writing, waiting readers have a higher priority than waiting writers (see Exercise 4.9).
Strategies R<W.1 and R<W.2 differ as follows. Assume that when writer W1 arrives, reader R1 is reading and another reader, R2, has requested to read but has not started reading. R<W.1 lets W1 start before R2, while R<W.2 lets W1 start after R2. For R<W.1 and R<W.2, readers starve if before a writer finishes writing, the next writer requests to write.
In strategy R<W.3, at the end of a writing, waiting readers have higher priority than waiting writers. Note that waiting readers are readers that have already requested to read and are waiting when the write completes, not readers who request to read after the write completes. This strategy does not create starving readers or writers. A waiting writer does not starve since the number of readers allowed to proceed at the end of each write is finite. A waiting reader does not starve since it will be allowed to proceed before the writers that request to write after this reader.
The R>W.1 implementation contains a timestamp that you can use in strategies R<W.2 and R<W.3 to order the requests (i.e., determine whether a request was issued earlier or later than another request).
import java.util.*;
final class requestMessage {
public requestMessage(int ID, boolean isRead) {this.ID = ID;
this.isRead = isRead;} |
|
public int ID; |
// ID of reader (0,1,2) or writer (0,1) |
308 |
MESSAGE PASSING |
public boolean isRead; |
// true if requesting to read |
||
int timeStamp; |
|
// value of clock when request is made |
|
} |
|
|
|
public final class readersAndWriters { |
|
||
public static void main (String args[]) { |
|
||
Controller c = new Controller(); |
|
||
Reader r0 = new Reader (c,0); |
Reader r1 = new Reader (c,1); |
||
Reader r2 = new Reader (c,2); |
|
|
|
Writer w0 = new Writer (c,0); |
Writer w1 = new Writer (c,1); |
||
c.setDaemon(true); c.start(); |
|
|
|
w0.start(); |
w1.start(); r0.start(); |
r1.start(); r2.start(); |
|
try{r0.join(); r1.join(); r2.join(); w0.join(); w1.join();} catch (InterruptedException e) {System.exit(1);}
}
}
final class Reader extends Thread { private int num;private Controller c;
Reader (Controller c, int num) { this.c = c;this.num = num;} public void run () {
try {
// random returns value between 0 and 1 using time of day as seed Thread.sleep((long)(Math.random()*1000)); // sleep 0...1 second
} catch (InterruptedException e) {} int value = c.read(num);
}
}
final classWriter extends Thread { private int num; private Controller c;
Writer (Controller c, int num) {this.c = c; this.num = num; } public void run () {
try {
// random returns value between 0 and 1 using time of day as seed Thread.sleep((long)(Math.random()*1000)); //sleep 0...1 second
} catch (InterruptedException e) {} c.write(num);
}
}
final class Controller extends Thread {
//Strategy R>W.1 : Many readers or one writer, with readers having a
//higher priority
final int numReaders = 3; final int numWriters = 2; private selectableEntry request = new selectableEntry(); // reader i calls entry startRead[i]
private selectableEntry[] startRead = new selectableEntry[numReaders]; private selectableEntry endRead = new selectableEntry();
EXERCISES |
309 |
// writer i calls entry startWrite[i]
private selectableEntry[] startWrite = new selectableEntry[numWriters]; private selectableEntry endWrite = new selectableEntry();
private boolean writerPresent = false;
private int readerCount = 0; private int sharedValue = 0;
private ArrayList readersQueue = new ArrayList(); // queue of reader IDs private ArrayList writersQueue = new ArrayList(); // queue of writer IDs private int clock = 1;
public Controller() {
for (int i = 0; i<numReaders; i++) startRead[i] = new selectableEntry(); for (int i = 0; i<numWriters; i++) startWrite[i] = new selectableEntry();
}
public int read(int ID) {
requestMessage req = new requestMessage(ID,true); try {request.call(req);} catch(InterruptedException e) {}
try {startRead[ID].call();} catch(InterruptedException e) {} int value = ID;
System.out.println ("Reader #" + ID + "Read " + value); try {endRead.call();} catch(InterruptedException e) {} return value;
}
public void write(int ID) {
requestMessage req = new requestMessage(ID,false); try {request.call(req);} catch(InterruptedException e) {}
try {startWrite[ID].call();} catch(InterruptedException e) {} sharedValue = ID;
System.out.println ("Writer #" + ID + "Wrote " + ID); try {endWrite.call();} catch(InterruptedException e) {}
}
publicvoid run() { try {
selectiveWait select = new selectiveWait();
select.add(request); |
// alternative 1 |
select.add(startRead[0]); |
// alternative 2 |
select.add(startRead[1]); |
// alternative 3 |
select.add(startRead[2]); |
// alternative 4 |
select.add(endRead); |
// alternative 5 |
select.add(startWrite[0]); |
// alternative 6 |
select.add(startWrite[1]); |
// alternative 7 |
select.add(endWrite); |
// alternative 8 |
while(true) { |
|
request.guard(true); |
|
//startRead if no writers writing and this reader is at head of the
//readersQueue
startRead[0].guard(!writerPresent && readersQueue.size() >
310 |
MESSAGE PASSING |
0 && ((requestMessage)readersQueue.get(0)).ID==0); startRead[1].guard(!writerPresent && readersQueue.size() > 0 && ((requestMessage)readersQueue.get(0)).ID==1); startRead[2].guard(!writerPresent && readersQueue.size() > 0 && ((requestMessage)readersQueue.get(0)).ID==2);
endRead.guard(true);
//startWrite if no readers reading or requesting and this
//writer is at head of the writersQueue startWrite[0].guard(!writerPresent && readerCount == 0 &&
readersQueue.size() == 0
&& writersQueue.size() > 0 && ((requestMessage) writersQueue.get(0)).ID==0);
startWrite[1].guard(!writerPresent && readerCount == 0 && readersQueue.size() == 0
&& writersQueue.size() > 0 && ((requestMessage) writersQueue.get(0)).ID==1);
endWrite.guard(true);
int choice = select.choose(); switch (choice) {
case 1: requestMessage req = (requestMessage)request. acceptAndReply();
// Note: timeStamps are not used in R>W.1 req.timeStamp = clock++; // save arrival time if (req.isRead ) // true when it’s a read request
readersQueue.add(req); else writersQueue.add(req); break;
case 2: startRead[0].acceptAndReply(); ++readerCount; readersQueue.remove(0); break;
case 3: startRead[1].acceptAndReply(); ++readerCount; readersQueue.remove(0); break;
case 4: startRead[2].acceptAndReply(); ++readerCount; readersQueue.remove(0); break;
case 5: endRead.acceptAndReply(); --readerCount; break; case 6: startWrite[0].acceptAndReply(); writerPresent = true;
writersQueue.remove(0); break;
case 7: startWrite[1].acceptAndReply(); writerPresent = true; writersQueue.remove(0); break;
case 8: endWrite.acceptAndReply(); writerPresent = false; break;
}
}
} catch (InterruptedException e) {}
}
}
EXERCISES |
311 |
5.8.In Section 5.5.2 we say that if program CP uses only links for communication and no selective wait statements, there is no nondeterminism that needs to be controlled during replay. But the implementation of class link uses P() and V() operations on semaphores. Chapter 3 describes a replay method for semaphores. Why is it that the P() and V() operations in the link class do not need to be controlled during replay?
6
MESSAGE PASSING IN DISTRIBUTED PROGRAMS
A distributed program is a collection of concurrent processes that run on a network of computers. Typically, each process is a multithreaded program that executes on a single computer. A process (or program) on one computer communicates with processes on other computers by passing messages across the network. The threads in a single process all execute on the same computer, so they can use message passing and/or shared variables to communicate.
In this chapter we examine lowand high-level mechanisms for message passing in distributed programs. Since Java provides a class library for network programming, we focus on Java. We also design our own Java message-passing classes and use them to develop distributed solutions to several classical synchronization problems. Entire books have been written about distributed programming, so this chapter is only a small introduction to a large topic. However, we hope to shed some new light on ways to trace, test, and replay distributed programs.
6.1 TCP SOCKETS
The channel objects in Chapter 5 were regular program objects shared by the threads in a single program. In this chapter we are working with a collection of programs that run on different machines. Since a program on one machine cannot directly reference objects in programs on other machines, channels can
Modern Multithreading: Implementing, Testing, and Debugging Multithreaded Java and C++/Pthreads/Win32 Programs, By Richard H. Carver and Kuo-Chung Tai Copyright 2006 John Wiley & Sons, Inc.
312
TCP SOCKETS |
313 |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Socket |
|
network |
|
Socket |
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Machine 1 |
|
|
|
Machine 2 |
|
Figure 6.1 Sockets are the endpoints of channels across a network.
no longer be implemented as regular program objects. Instead, channels will be formed across a communications network, with help from the operating system.
When two threads want to exchange messages over a channel, each thread creates an endpoint object that represents its end of the network channel. The operating system manages the hardware and software that is used to transport messages between the endpoints (i.e., “across the channel”). These endpoint objects are called sockets (Fig. 6.1):
žThe client thread’s socket specifies a local I/O port to be used for sending messages (or the I/O port can be chosen by the operating system). The client’s socket also specifies the address of the destination machine and the port number that is expected to be bound to the server thread’s socket.
žThe server’s socket specifies a local I/O port for receiving messages. Messages can be received from any client that knows both the server’s machine address and the port number bound to the server’s socket.
žThe client issues a request to the server to form a connection between the two sockets. Once the server accepts the connection request, messages can be passed in either direction across the channel.
6.1.1 Channel Reliability
Messages that travel across a network may be lost or corrupted, or they may arrive out of order. Some applications may be able to tolerate this. For example, if the data being sent across the network represents music, losing a message may create only a slight distortion in the sound. This might sound better to the listener than a pause in the music while lost messages are resent.
An application can choose how reliably its messages are transmitted by selecting a transport protocol:
žTransmission Control Protocol (TCP) ensures the reliable transmission of messages. TCP guarantees that messages are not lost or corrupted and that messages are delivered in the correct order. However, this adds some overhead to message transport.
žUser Data Protocol (UDP) is a fast but unreliable method for transporting messages. Messages sent using UDP will not be corrupted, but they may be lost or duplicated, and they may arrive in an order different from the order
314 |
MESSAGE PASSING IN DISTRIBUTED PROGRAMS |
in which they were sent. If this is not acceptable, the application must take care of error handling itself, or use TCP instead.
Both TCP and UDP utilize the Internet Protocol (IP) to carry packets of data. The IP standard defines the packet format, which includes formats for specifying source and destination addresses and I/O ports.
6.1.2 TCP Sockets in Java
The java.net class library provides classes Socket and ServerSocket for TCPbased message passing.
Class Socket We will use a simple client and server example to illustrate the use of TCP sockets in Java. A client’s first step is to create a TCP socket and try to connect to the server:
InetAddress host; |
// server’s machine address |
int serverPort = 2020; |
// port number bound to |
|
// server’s socket |
Socket socket; |
// client’s socket |
try {host = InetAddress.getByName(‘‘www.cs.gmu.edu’’); } |
|
catch (UnknownHostException) { ... } |
|
try {socket = new Socket(host,serverPort); } |
// create a socket and |
|
// request a connection to host |
catch (IOException e) { ... } |
|
The client assumes that the server is listening for TCP connection requests on port serverPort. The Socket constructor throws an IOException if it cannot make a connection.
When the client’s request is accepted, the client creates an input stream to receive data from its socket and an output stream to send data to the socket at the server’s end of the channel. To the programmer, sending and receiving messages using TCP sockets looks just like reading and writing data from files:
PrintWriter toServer = new PrintWriter(socket.getOutputStream(),true); BufferedReader fromServer = new BufferedReader(new inputStreamReader
(socket.getInputStream())); |
|
toServer.println("Hello"); |
// send a message to the |
|
// server |
String line = fromServer.readLine(); |
// receive the server’s reply |
System.out.println ("Client received: "+ line + ‘‘ from Server’’); toServer.close(); fromServer.close(); socket.close();
A read operation on the InputStream associated with a Socket normally blocks. It is possible to set a timeout so that a read operation will not block for more
