Lecture 17 - 19 (Images, text, links)

 Mutual Exclusion With Sleep and Wakeup
 

 Semaphores on the Railroad and Their Computer Scientific Definition
    Semaphore Explained in Children's Terms

 Semaphore Power: 3 applications

Producer-Consumer problem solved by semaphores: Tanenbaum Fig. 2-24

There are 3 kinds of resources:
The buffer: only one thread at a time may access the buffer.  We have seen race bugs in unprotected buffers.  The buffer is protected by a semaphore named mutex.

Empty buffer slots, like empty appartments, are resources.  The semaphore named empty holds the number of empty slots as its value.

Occupied buffer slots are resouces.  They hold the data items produced by the producer and consumed by the consumer.  Semaphore full holds the number of data items currently stored in the buffer as its value.

Lecture 18 resumes here.


  Readers and Writers Problem Definition and Idea for solution using Semaphores

 Programming Style Criticism-Please Name Objects to Document their Purpose
Solution to the Readers and Writers Problem from Tanenbaum Fig. 2-35 discussed in detail.

Lecture 19


 The Waiting Mutex Surveyed
 

 A common pattern makes programming easier.
 

 The Wait Idiom
 

 Wait and Signal Operations in MONITORS

What is a monitor?   2 features:
(1) Set of functions where the body of each one is a critical section (region) for a common mutex.

(The programming language automatically inserts the lock and unlock operations.)
(2) Set of  condition variables where each one can be wait()'d or signal()'d from within a  monitor function body.

Monitors in the form defined by Brinch Hanson are not implemented in today's popular languages.  Their effect can be programmed in ways depending on the system environment (POSIX and C, for example) or the capabilities of certain languages (especially Java).

POSIX  with pthreads:

mutex m;
condition c;
lock(m);
unlock(m);
condition_wait(c,m); (Both c and m are parameters).
condition_signal(c);
should be used in a disciplined manner (see below).

Java:

Each object can "be" the monitor whose functions are its synchronized methods.
wait(); makes the current thread sleep.
notify(); awakens one thread sleeping in the monitor.
notifyAll(); awakens all threads sleeping in the monitor.
The secret of Java:
An object is a structured variable: It holds data called its value or state.
For example, new String("Hi"); creates or instantiates a String type object that contains the character sequence "Hi".   In terms of Java, we say this object belongs to the String class.

In the Java code String s = String("Hi"); the variable s is NOT A STRING!! s is a reference variable which is made, by the assignment operator, to hold a reference to the actual String object.  A reference is (really implenented by) a POINTER, familiar from C++ and CSI310.

Therefore, if we code String t; t = s; t.append("There"); System.out.println( s );
we get "HiThere" printed, NOT "Hi"
 

Condition Variables in POSIX:  Programmers must carefully code critical regions protected by mutex's because C/C++ has NO language support for monitors.
Posix pthreads condition variables must be used in conjunction with mutexes : Producer-Consumer Example from Sun :
 

My notes:
  1. The buffer object includes a mutex plus 2 condition variables.
  2. The assert() macro makes the program crash (with a message) to alert the programmer that there is a bug.
    1. STRONGLY ENCOURAGED for debugging and expressing invariants for readability.
    2. DO NOT USE for checking for runtime errors and do not put normal operations (like incrementing or setting a variable) into an assertion:
      1. A runtime error is a situation that is expected to happen because of the current exection environment.
      2. When software is compiled for production,  the NODEBUG preprocessor is defined which turns  assert()statements into empty statements.
  3. The posix condition variable merely controls which thread is awakened.  The actual condition test is arbitrary C code.
  4. The condition must be reevaluated whenever wait returns.  Sun's recommended pattern:
    1.  
          pthread_mutex_lock();
              while(condition_is_false)
                  pthread_cond_wait();
          We now know the condtion is true, provided all code that can falisify it has been protected by the mutex.(sdc)
          pthread_mutex_unlock();
POSIX System Programming Digression:  Handling interrupted blocked system calls .
Should the system try to restart them or should they return with errno set to EINTR?
I roughly interpret the  POSIX answer to be that for old fashioned  blocking system calls, the system should by default not restart them automatically (because this gives the programmers flexibility to program whether to give up or restart depending on the situation.  However, sigaction() should be used to install signal handlers in new software, so the programmer can control with the SA_RESTART flag whether or not the system shall restart.

For modern POSIX facilities supporting multiple threads, some functions will never return with EINTR, and others might on old systems.

Here's a page of the relevent quotes from POSIX.


Example 4-11 The Producer/Consumer Problem and Condition Variables

typedef struct {
    char buf[BSIZE];
    int occupied;
    int nextin;
    int nextout;
    pthread_mutex_t mutex;
    pthread_cond_t more;            The more condition signifies the buffer has at least one item in it.
    pthread_cond_t less;              The less condition signifies the buffer has at least one empty slot.
} buffer_t;

buffer_t buffer;



Example 4-12 The Producer/Consumer Problem-the Producer

void producer(buffer_t *b, char item)
{
    pthread_mutex_lock(&b->mutex);

    while (b->occupied >= BSIZE)
        pthread_cond_wait(&b->less, &b->mutex);

    assert(b->occupied < BSIZE);

    b->buf[b->nextin++] = item;

    b->nextin %= BSIZE;
    b->occupied++;

    /* now: either b->occupied < BSIZE and b->nextin is the index
       of the next empty slot in the buffer, or
       b->occupied == BSIZE and b->nextin is the index of the
       next (occupied) slot that will be emptied by a consumer
       (such as b->nextin == b->nextout) */

    pthread_cond_signal(&b->more);

    pthread_mutex_unlock(&b->mutex);
}



Example 4-13 The Producer/Consumer Problem-the Consumer
 
char consumer(buffer_t *b)
{
    char item;
    pthread_mutex_lock(&b->mutex);
    while(b->occupied <= 0)
        pthread_cond_wait(&b->more, &b->mutex);

    assert(b->occupied > 0);

    item = b->buf[b->nextout++];
    b->nextout %= BSIZE;
    b->occupied--;

    /* now: either b->occupied > 0 and b->nextout is the index
       of the next occupied slot in the buffer, or
       b->occupied == 0 and b->nextout is the index of the next
       (empty) slot that will be filled by a producer (such as
       b->nextout == b->nextin) */

    pthread_cond_signal(&b->less);
    pthread_mutex_unlock(&b->mutex);

    return(item);
}

Threads and Monitors in Java:
Monitor Solution for the Producer-Consumer Problem in Java: Tanenbaum Fig. 2-28.  You can read the basic rules for using "synchronized methods" in Tanenbaum.  The set of synchronized methods belonging to one class behaves like one  monitor for each class instance.  (It is not strictly a monitor because Java has no explicit condition variables.  The object whose synchronized methods call Java's wait() and notify() plays the role of a both a condition variable and a mutex.)
 
  1. The producer thread runs a function which has a local variable int item;
  2. The consumer thread runs a function which has a  (separate) local variable int item;
  3. There is one our_monitor object that has variables buffer (array of integers), and  count, lo and hi (integers)
  4. The producer and consumer functions sometimes call the insert() and remove() functions belonging to the (single) monitor object.
  5. mon is a class variable belonging to the ProducerConsumer class.  Its value refers to the the single, monitor object.  This variable is accessible from each of the 2 functions run by the 2 threads.  (These 2 functions are both named run because a Java thread must have a method named run. A Java thread calls this method when it begins running.)
We then displayed and walked through the code from Fig. 2-28 from Tanenbaum.
One instance of the our_monitor class, referred to by variable mon, inplements the entire buffer which includes it's storage space, variables to index the next place to insert an item and the next place to remove an item, a count of how many items in the buffer now, plus the synchronized methods insert() and remove() that belong to the monitor.