Lecture 22

SFR1811·2022년 3월 15일
0

CS343-Parallel Programming

목록 보기
19/24

8.7 Monitor Types

  • explicit scheduling
    • you indicate a task to wait or resume
    • internal scheduling (uCondition)
    • external scheduling (accept)
  • implicit scheduling
    • monitor automatically makes a task to wait or resume
    • monitor selects (enter/exit)

Explicit Signal

General Model of Monitor:

When an active task signals blocked task in conditions, it moves the task from conditions to signalled and the active task goes to signaller to empty the monitor.

  • this is not necessarly the case for all monitors, but this kind of picture helps generalizing the rules. there may be optimizations.

The difference between monitors from different languages is once the monitor is empty, which is going to be pulled first from calling, signalled, signaller.


calling (C), signalled (W), signaller(S)

  • 1~2
    • It has barging prevention
  • 3~4
    • C = W or C = S may result in barging, but with barging avoidance implementations, it can prevent starvation.
  • 5~6
    • making W and S having the same priority results in confusing behavior
  • 7~13
    • If calling has greater priority than any one of signalled or signaller, it may cause starvation because calling has unbounded incoming tasks.

Implicit Signal

Monitor either have an explicit signal - statement - or an implicit signal - automatic signal.

Implicit signal has no condition variable or explicit signal statement.

But it has a waitUntil statement

_Monitor BoundedBuffer{
  int front=0, back=0, count=0;
  int elements[20];
 public:
  _Nomutex int query() const {return count;}
  void insert (int elem){
    waitUntil count != 20; // not in uC++
    elements[back] = elem;
    back = (back+1)%20;
    count += 1;
  }
  void remove(){
    waitUntil count != 0;
    elements[front] = elem;
    back = (front+1)%20;
    count -= 1;
    return elem;
  }
}

This is not efficient because we are not doing any cooperation. We are leaving everything to the monitor to figure out.

** Assignment note: there are 3 textbook solutions for implementing waitUntil by your self


Summery

first two are explicit signaling and 3rd is implicit.


Coroutine Monitor

_Mutex _Coroutine = _Cormonitor

_Cormonitor{
  void main(){
    ... suspend() ... 
    ... suspend() ... 
  }
 public:
  void m1(...){... resume(); ...} // mutual exclusion
  void m2(...){... resume(); ...} // mutual exclusion
  ... // destructor is ALWAYS mutex
};

This allows a coroutine from being accessd from multiple threads.


8.8 Java Monitor

Java has synchronized class members - _Mutex members but incorrectly named

Java synchronized methods have 3 methods

wait();
notify();
notifyAll();

you don't know the name of the condition variable, neither it gives you.

class Buffer{
  // buffer declarations
  private int count = 0;
  public synchronized void insert (int elem) {
    while(count == Size) wait(); // busy waiting
    //add to buffer
    copunt += 1;
    if (count == 1) notifyAll();
  }
  public synchronized int remove() {
    while (count == 0) wait(); // busy waiting
    // remove from buffer
    count -= 1;
    if (count == Size -1) notifyAll();
    return elem;
  }
}

Java monitor is of type non-priority non-blocking monitor.

So it wakes C = W < S
There does exists barging, so the threads must busy wait.


Java Barrier

class Barrier {
  private int N, count = 0;
  public Barrier(int N){this.N = N;}
  public synchronized void block(){
    count += 1;
    if (count < N){
      try{wait();}
      catch(InterruptedException e){}
    }else{
      notifyAll();
    }
    count -= 1;
  }
}

This does not work because of barging.

  1. When the last task notifies all waiting task
  2. Another task can barge in and think that itself is the last task
  3. So instead of N tasks being passed, there can be N+1 tasks passing through

If the Barrier race was in a loop, lets say 15 runs,

  1. the last task notifies all, makes count = N-1
  2. races back in and calls block()
  3. thinks its the last task, and notifies all
  4. back to step 2

and finish all 15 runs before other tasks even complets their first run.


SO how do we solve this?

Attempt 1: instead of

    }else{
      notifyAll();
    }
    count -= 1;

do this

    }else{
      count = 0;
      notifyAll();
    }

This does not work because of spurious wakeup
This means A WAITING THREAD CAN JUST WOKEN UP AT RANDOM - this typically happens due to barging. A thread gets woken up but before it run, another thread runs in and changes the condition to a invalid condition.

So...
Attempt 2: lets put wait clause in a loop with a ticket of barrier run number

class Barrier {
  private int N, count = 0, generation = 0;
  public Barrier(int N){this.N = N;}
  public synchronized void block(){
    int mygen = generation;
    count += 1;
    if (count < N){
      while(mygen == generation){
        try{wait();}
        catch(InterruptedException e){}
      }
    }else{
      count = 0;
      generation += 1;
      notifyAll();
    }
  }
}

The loop waiting is a kind of barging avoidence.


What if we just make condition variables?

This does not work because when it calls Condition::Wait(), it just ran into a nested-monitor problem

profile
3B CS

0개의 댓글