Concurrency

  1. If we have multiple processes or threads working on a problem, they may need to coordinate.
  2. If we have multiple processes or threads competing for resources, they may need to be managed.
  3. Terminology:
    1. Atomic operation. One or more statements that cannot be interrupted.
    2. Critical section. Area of code that accesses a resource such that no other executing unit (EU) can be allowed to access that resource at the same time.
    3. deadlock. Situation where multiple EUs get stuck because they are all waiting for each other.
    4. livelock. Similar to deadlock, but the EUs keep running, they just never get past some place.
    5. Mutual exclusion. The thing that makes EUs not step on each other's critical sections.
    6. race condition. Multiple EUs accessing the same resource at the same time result in indeterminate output depending on who gets run first.
    7. Situation where an EU is not stuck, but might have to theoretically wait forever because other EUs keep hoggin the resource.
  4. Race condition
    1. Multiple EUs operating on same variables:
      
      out = getchar();
      putchar(out);  
      	

      depends on who runs when.

    2. Simple solution- single access. only one EU can access at a time. Others must wait.
    3. Also want to avoid deadlock and starvation. Want to always let EU use resource if no one else is.
    4. Assume use is always finite.
  5. Hardware solutions
    1. Disable interrupts? A bit drastic and only works on uniprocessors.
    2. Special instructions. Compare&Swap, exchange
    3. Why does that have to be atomic?
  6. Advantages
    1. Supports many EUs and many sections.
    2. easy to verify.
  7. disadvantages
    1. Busy wait wasteful
    2. starvation
  8. Semaphores

    
          while(true) {
             semWait(s);
             /*critical section*/
             semPost(s);
          }
        

  9. Producer/Consumer Problem
    1. One or more "producers" are adding data to a buffer.
    2. One or more "consumers" are removing from the buffer.
    3. Obvious example: network buffer.
    4. Producers stop when the buffer is full.
    5. Consumers stop when the buffer is empty.
    6. Only one EU can be actually reading or writing to the buffer at a time.
    7. Assume functions produce(), to produce an item, append() to add item to buffer, take() to remove item, and consume() to consume.
    8. We will start with a simplified version: 1 P, 1 C and an infinite buffer
  10. 1/1/I
    1. With an infinite buffer, producer never needs to block, so we need to just protect the critical section and have the consumer wait on empty.
    2. Why is this broken?
      
      void consumer() { 
        if (n==0) sem_wait(empty);
        sem_wait(s);
        take();
        n--;
        sem_post(s);
        consume();
      }
      
      void producer() {
        while(1) {
          produce();
          sem_wait(s);
          append();
          n++;
          if (n==1) sem_post(empty);
          sem_post(s);
        }
      }
      

      This works better:

      
      void consumer() { 
        sem_wait(n);
        sem_wait(s);
        take();
        sem_post(s);
        consume();
      }
      
      void producer() {
        while(1) {
          produce();
          sem_wait(s);
          append();
          sem_post(s);
          sem_post(n);
        }
      }

  11. Bounded buffer (circular array)
    
     e = sizeofbuffer; n = 0;
     void consumer() { 
       sem_wait(n);
       sem_wait(s);
       take();
       sem_post(s);
       sem_post(e);
       consume();
     }
     
     void producer() {
       while(1) {
         produce();
         sem_wait(e)
         sem_wait(s);
         append();
         sem_post(s);
         sem_post(n);
       }
     }

  12. Monitors
    1. There are other ways to do this. One common one (often seen in Java) is monitors.
    2. Monitors are software modules that organize code, exactly like classes, but with the following properties:
      1. Data in the monitor is only accessed from within the monitor
      2. An EU "enters" monitor by calling a monitor function.
      3. Only 1 process can be "in" a monitor at a time.
    3. Monitors also contain mechanizms for synchronization.
      1. There are the special variables called condition variables that live in the monitor.
      2. wait(c)- causes the EU to block until that condition is true. Exits monitor.
      3. signal(c)- unblock the next thread on the condition queue for that condition.
      4. basic solution to producer/consumer - put both take and append in monitor. Signal and wait for over/underflow:
      
       void append(x) {
         if (count == n) wait(full);
         buffer[next]= x;
         next = (next+1)%N;
         count++;
         signal(empty);
       }
      
       void take(x) {
         if (count == 0) wait(empty);
         x = buffer[next];
         next = (next-1)%N;
         count--;
         signal(full);
       }
      
       void producer() {
         while (1) {
           produce(x);
           append(x);
         }
      
       void consumer() {
         while (1) {
           take(x);
           consume(x);
         }
      

    4. In Java, every object has a monitor associated with it automatically, but a method is not part of the monitor unless you use the word synchronized.
  13. Message Passing.
    1. When processes interact with one another, two fundamental requirements must be satisfied: synchronization and communication.
    2. Message Passing is one solution to the second requirement
    3. send(dest,message), recv(source,message)
    4. must send before you receive. Synchronization!
    5. Blocking on send and receive creates tight synchronization, rendezvous.
    6. non-blocking, more flexible, now requires other synch.
    7. How do we address the message?
      1. Direct- PID, etc.
      2. Indirect, Mailbox.
    8. Mutex using messages- use a blocking receive at start, a send at end.
    9. How to solve the producer/consumer problem with a sahred buffer and messages? Don't, just send the data in the messages!
  14. Readers and Writers.
    1. A data area is shared among many processes
    2. Some processes only read the data area, some only write to the area
      1. Multiple readers may read the file at once.
      2. Only one writer at a time may write.
      3. If a writer is writing to the file, no reader may read it.
    3. Subtly different from a Producer/Consumer problem.
    4. Here wsem is used to keep out all other processes when there is a writer going.
    5. The basic plan for readers is: if there's already a reader in the CS, I can just go right in. If I'm the first reader to come along, I better check to make sure there isn't alreader a writer in there. To see if I'm the first, we keep track of how many are there with readcount.
    6. x is just a critical section wrapper around readcount.
    7. In this version a reader waits if a writer is writing, but otherwise readers rule the day. If readers keep coming in, writers starve.
    8. This is bad because in most cases, there can be lots of readers.
    9. We still use wsem, readcount and x, but we need more.
    10. The writers will keep track of how many they are, so we need a writecount variable, and a mutex semephore y.
    11. When a writer shows up, it checks to see if it's first. If it is, there still might be a reader, so it will need to check and possibly block. Instead of using wsem to control all access, we'll use wsem to keep out other writers, and rsem to keep out the the readers when the writer is writing, and to keep out the writer when it .
    12. Let's see it in action.
      Readers still blocked.Remaining readers go.Last one.and done.
      Process Step rsem wsem x y z readcount writecount comment
      none n/a 1 1 1 1 1 0 0
      r1 semWait(z) 1 1 1 1 0 0 0
      r1 semWait(rsem) 0 1 1 1 0 0 0
      r1 emWait(x) 0 1 0 1 0 0 0
      r1 readcount++ 0 1 0 1 0 1 0
      r1 semWait(wsem) 0 0 0 1 0 1 0lock out writers
      r1 semSignal(x) 0 0 1 1 0 1 0
      r1 semSignal(rsem) 1 0 1 1 0 1 0
      r1 semSignal(z) 1 0 1 1 1 1 0
      swap
      r2 semWait(z) 1 0 1 1 0 1 0
      r2 semWait(rsem) 0 0 1 1 0 1 0
      r2 semWait(x) 0 0 0 1 0 1 0
      r2 readcount++ 0 0 0 1 0 2 0
      r2 semSignal(x) 0 0 1 1 0 2 0
      r2 semSignal(rsem) 1 0 1 1 0 2 0
      r2 semSignal(z) 1 0 1 1 1 2 0
      swap
      r3 semWait(z) 1 0 1 1 0 2 0
      r3 semWait(rsem) 0 0 1 1 0 2 0
      r3 semWait(x) 0 0 0 1 0 2 0
      r3 readcount++ 0 0 0 1 0 3 0
      r3 semSignal(x) 0 0 1 1 0 3 0
      r3 semSignal(rsem) 1 0 1 1 0 3 0
      r3 semSignal(z) 1 0 1 1 1 3 0
      swapWe now have 3 readers reading
      w1 semWait(y) 1 0 1 0 1 3 0
      w1 writecount++ 1 0 1 0 1 3 1
      w1 semWait(rsem) 0 0 1 0 1 3 1 Am I first? Lock out new readers.
      w1 semSignal(y) 0 0 1 1 1 3 1
      w1 semwait(wsem) 0 -1 1 1 1 3 1
      blockreaders have locked us out
      w2 semwait(y) 0 -1 1 0 1 3 1
      w2 writecount++ 0 -1 1 0 1 3 2
      w2 semSignal(y) 0 -1 1 1 1 3 2
      w2 semWait(wsem) 0 -2 1 1 1 3 2
      block2 writers waiting on the readers.
      r4 semWait(z) 0 -2 1 1 0 3 2Oh no! will new readers starve our writers?
      r4 semWait(rsem) -1 -2 1 1 0 3 2
      blockSee how we're blocked before releasing z?
      r5 semWait(z) -1 -2 1 1 -1 3 2
      blockAny new readers will block on z.
      r1 semWait(x) -1 -2 0 1 -1 3 2The early ones finish
      r1 readcount-- -1 -2 0 1 -1 2 2
      r1 semSignal(x) -1 -2 1 1 -1 2 2
      doneStill readers in there.
      r2 semWait(x) -1 -2 0 1 -1 2 2
      r2 readcount-- -1 -2 0 1 -1 1 2
      r2 semSignal(x) -1 -2 1 1 -1 1 2
      doneOne left.
      r3 semWait(x) -1 -2 0 1 -1 1 2
      r3 readcount-- -1 -2 0 1 -1 0 2
      r3 semSignal(wsem) -1 -1 0 1 -1 0 2Last one out, wake up the writers
      r3 semSignal(x) -1 -1 1 1 -1 0 2
      doneWriters ready to go.
      w3 semWait(y) -1 -1 1 0 -1 0 2new writer, what order will they run?
      w3 writecount++ -1 -1 1 0 -1 0 3
      w3 semSignal(y) -1 -1 1 1 -1 0 3
      w3 semWait(wsem) -1 -2 1 1 -1 0 3
      block
      w1 semSignal(wsem) -1 -1 1 1 -1 0 3Writer completes, wakes next writer
      w1 semWait(y) -1 -1 1 0 -1 0 3
      w1 writecount-- -1 -1 1 0 -1 0 2
      w1 semSignal(y) -1 -1 1 1 -1 0 2
      done
      w2 semSignal(wsem) -1 0 1 1 -1 0 2
      w2 semWait(y) -1 0 1 0 -1 0 2
      w2 writecount-- -1 0 1 0 -1 0 1
      w2 semSignal(y) -1 0 1 1 -1 0 1
      done
      w3 semSignal(wsem) -1 1 1 1 -1 0 1
      w3 semWait(y) -1 1 1 0 -1 0 1
      w3 writecount-- -1 1 1 0 -1 0 0
      w3 semSignal(rsem) 0 1 1 0 -1 0 0Last writer out, wake the readers
      w3 semSignal(y) 0 1 1 1 -1 0 0
      done
      r4 semWait(x) 0 1 0 1 -1 0 0
      r4 readcount++ 0 1 0 1 -1 1 0
      r4 semWait(wsem) 0 0 0 1 -1 1 0
      r4 semSignal(x) 0 0 1 1 -1 1 0
      r4 semSignal(rsem) 1 0 1 1 -1 1 0
      r4 semSignal(z) 1 0 1 1 0 1 0Waking other reader
      r4 semWait(x) 1 0 0 1 0 1 0
      r4 readcount-- 1 0 0 1 0 0 0
      r4 semSignal(wsem) 1 1 0 1 0 0 0
      r4 semSignal(x) 1 1 1 1 0 0 0
      done
      r4 semWait(rsem) 0 1 1 1 0 0 0
      r4 semWait(x) 0 1 0 1 0 0 0
      r4 readcount++ 0 1 0 1 0 1 0
      r4 semWait(wsem) 0 0 0 1 0 1 0
      r4 semSignal(x) 0 0 1 1 0 1 0
      r4 semSignal(rsem) 1 0 1 1 0 1 0
      r4 semSignal(z) 1 0 1 1 1 1 0
      r4 semWait(x) 1 0 0 1 1 1 0
      r4 readcount-- 1 0 0 1 1 0 0
      r4 semSignal(wsem) 1 1 0 1 1 0 0
      r4 semSignal(x) 1 1 1 1 1 0 0
      done