We are what we repeatedly do. Excellence, then, is not an act, but a habit. AristotleInterprocess Communication ( IPC )
Too much milk problem ( Adopted from Barton P. Miller's Lecture notes )
| Person A | Person B | |
3:00 3:05 3:10 3:15 3:20 3:25 3:30 |
Look in fridge. Out of milk. Leave for store. Arrive at store. Leave store. Arrive home, put milk away. |
Look in fridge. Out of milk. Leave for store. Arrive at store. Leave store. Arrive home. OH NO! |
What does correct mean?
Synchronization: the use of atomic operations to ensure the correct operation of cooperating processes
race conditions
shared resource -- critical resource
Mutual exclusion: Mechanisms that ensure that only one person or process is doing certain things at one time (others are excluded). E.g. only one person goes shopping at a time.
Critical section: A section of code, or collection of operations, in which only one process may be executing at a given time. E.g. shopping. It is a large operation that we want to make "sort of" atomic.
There are many ways to achieve mutual exclusion. Most involve some sort of locking mechanism: prevent someone from doing something. For example, before shopping, leave a note on the refrigerator.
Three elements of locking:
| 1. | Must lock before using. | leave note |
| 2. | Must unlock when done. | remove note |
| 3. | Must wait if locked. | do not shop if note |
| Processes A & B | |
1 2 3 4 5 6 7 |
if (NoMilk) {
if (NoNote) {
Leave Note;
Buy Milk;
Remove Note;
}
}
|
Does this work?
What happens if we leave the note at the very beginning: does this make everything work?
| Process A | Process B | |
1 2 3 4 5 6 |
if (NoNote) {
if (NoMilk) {
Buy Milk;
}
Leave Note;
}
|
if (Note) {
if (NoMilk) {
Buy Milk;
}
Remove Note;
}
|
Suppose B goes on vacation. A will buy milk once and will not buy any more until B returns. Thus this really does not really do what we want; it is unfair, and leads to starvation.
| Process A | |
1 2 3 4 5 6 7 |
Leave NoteA;
if (NoNoteB) {
if (NoMilk) {
Buy Milk;
}
}
Remove NoteA;
|
What can we say about this solution?
Solution is almost correct. We just need a way to decide who will buy milk when both leave notes (somebody has to hang around to make sure that the job gets done).
Process B stays the same as before.
| Process A | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Leave NoteA;
if (NoNoteB) {
if (NoMilk) {
Buy Milk;
}
} else {
while (NoteB) {
DoNothing;
}
if (NoMilk) {
Buy Milk;
}
}
Remove NoteA;
|
How do we know this is correct?
This solution works. But it still has two disadvantages:
Good solution requirements of mutual exclusion
problem: if a process forgot to turn on interrupts, other processes can't be executed
mulitprocessing -- time-consuming and difficult to disable all interrupts
strict alternation
| P0 | P1 | |
|---|---|---|
while ( 1 ) {
while ( turn != 0 )
; //wait
critical_section();
turn = 1; //P1's turn now
non_critical_section();
}
|
while ( 1 ) {
while ( turn != 1 )
; //wait
critical_section();
turn = 0; //P0's turn now
non_critical_section();
}
|
The non_critical_section() of say P1 could be very long. This may lead to P0 blocking itself.
Peterson's Solution
| P0 | P1 | |
|---|---|---|
//global variables int wait; //0 or 1 int interested[2]; //interested to enter C.S. |
||
void enter_region0() {
interested[0] = 1; //P0 interested in C.S.
wait = 0; //P0, please wait first
while ( wait == 0 && interested[1] )
; //wait, if the other is interested
}
void leave_region0() { //P0 leaving C.S.
interested[0] = 0;
}
|
void enter_region0() {
interested[1] = 1; //P1 interested in C.S.
wait = 1; //P1,please wait first
while ( wait == 1 && interested[0] )
; //wait, if the other is interested
}
void leave_region1() { //P1 leaving C.S.
interested[1] = 0;
}
|
|
Lamport's Bakery Algorithm
Process i
boolean choosing[n];
int number[n];
while (true)
{
choosing[i] = true; //picking a number
//max ( number[0], ..., number[n-1] ) + 1
number[i] = 1 + getmax( number[], n );
choosing[i] = false; //finished picking a number
for (int j=0; j < n; j++ ) //check with others
{
while ( choosing[j] ) //don't do anything while someone
; //is choosing a #
while (( number[j]!=0 ) && ( number[j],j) < ( number[i],i ))
; //check if process j has higher priority
//( lower # )
}
critical_section();
number[i] = 0; //reset
non_critical_section();
}
|
while ( 1 ) {
test_and_set(); //lock ← 1; wait if necessary
critical_section();
reset(); //lock ← 0
}
|
o A semaphore S is an integer variable, apart from initialization is accessed through standard atomic operations:
void wait ( int S ) { //atomic
while ( S <= 0 )
; //wait
S = S - 1;
}
|
o invented by Dijkstra in 1965
o Semaphores are simple and elegant and allow the solution of many interesting problems. They do a lot more than just mutual exclusion.
o Binary semaphores are those that have only two values, 0 and 1. They are implemented in the same way as regular semaphores except multiple signal()s will not increase the semaphore to anything greater than one.
o Semaphores are not provided by hardware. But they have several attractive properties:
o Here is an example of how semaphores are used to enforce mutual exclusion:
//n processes share semaphore mutex ( mutual exclusion )
Semaphore mutex; //initialized to 1
while ( 1 ) {
wait ( mutex );
critical_section();
signal ( mutex );
non_critical_section();
}
|
o When a process executes the wait() operation and finds that the semaphore value is not positive, it must wait.
Example
In figure A, there are three semaphores: S1 has a value of zero and process P1 is blocked on S1; S2 has a value of three, meaning three more processes may execute a down operation on S2 without being put to sleep; S3 has a value of zero, and has two sleeping processes, P4 and P5, blocked on it.

Now let us look at what happens when a process executes an UP on S1. In figure B, process P1 is activated by an UP operation. It executes a DOWN on S1 and enters its critical section. The state of the semaphores after these actions has now changed.

o C implementation
typedef int process;
typedef struct {
int count;
process Q[MAX_P]; //a queue of processes
} Semaphore S;
Semaphore S;
|
o A Java implementation
Weakness:
send ( P, &message ); //send a message to process P receive ( Q, &message );//receive a message from Q |

A communication link has to satisfy the following properties:
Example: Producer-consumer problem
#define N 100 //# of slots in buffer
void producer()
{
int item;
message m; //message buffer ( ~plate )
while ( true ) {
produce_item ( &item ); //generate something to put in buffer
receive ( consumer, &m ); //wait for an empty buffer to arrive
build_message ( &m, item ); //construct a message to send
send ( consumer, &m ); //send item to consumer
}
}
|
indirect communication
mailbox ( in UNXI, this is called pipes )
send ( A, &message ) //send a message to mailbox A
receive ( A, &message ) //receive a message from mailbox A
e.g. process P → process Q
send ( Q, &message );
receive ( Q, &message );
receive ( P, &message );
send ( P, "acknowledgement" );
Remote Procedure Call ( RPC )

stub procedures

The dining philosophers problem ( Dijkstra, 1965 )

Analysis

//philosopher i
while ( true ) {
think();
wait ( mutex ); //first check if someone is eating
take_chops( i ); //take left chopstick
take_chops((i+1)%5); //take right chopstick
eat();
put_chops( i ); //put down left chops
put_chops( (i+1)%5); //put down right chops
signal( mutex ); //wake up anyone who's waiting
}
|
O.K. It works! But what's the drawback of the above algorithm?
Improvement:

deadlock

starvation

//philosopher i
#define LEFT ( i - 1 ) % 5
#define RIGHT ( i + 1 ) % 5
typedef int semaphore;
semaphore s[5] = {0, 0, 0, 0, 0};
semaphore mutex = 1;
void philosopher ( int i )
{
while ( 1 ) {
think();
pickup ( i ); //pick up chops
eat();
putdown( i ); //put down chops
}
}
|