Lecture 18

18283 ワード

6.4.3 Lock Techniques
Locking patterns are used to handle complicated critical sections.
Split binary semaphore
have group of semaphores
treat them as if they are single binary semaphore
i.e. sum of all semaphores are either 0 or 1.
Used when different kinds of tasks have to block separately.
Baton passing
a thread wakes up with a lock locked.
baton is conceptually accuired but doesn't really exists.
Used to force some thread to execute without barging.
class BinSem {
  queue<Task> blocked;
  bool avail;
  SpinLock lock;
 public:
  BinSem( bool start = true ) : avail( start ) {}
  void P() {
    lock.acquire(); PICKUP BATON, CAN ACCESS STATE
    if ( ! avail ) {
      // add self to lock’s blocked list
      PUT DOWN BATON, CANNOT ACCESS STATE
      yieldNoSchedule( lock );
      // UNBLOCK WITH SPIN LOCK ACQUIRED
      PASSED BATON, CAN ACCESS STATE
    }
    avail = false;
    lock.release(); PUT DOWN BATON, CANNOT ACCESS STATE
  }
  void V() {
    lock.acquire(); PICKUP BATON, CAN ACCESS STATE
    if ( ! blocked.empty() ) {
      // remove task from blocked list and make ready
      PASS BATON, CANNOT ACCESS STATE
    } else {
      avail = true;
      lock.release(); PUT DOWN BATON, CANNOT ACCESS STATE
    }
  }
};
6.4.4 Readers and Writer Problem
Program reads more than write and reading does not conflict with each other.
You want to allow multiple readers to have simultaneous access,
but serialize access for writer tasks.

Solution 1
There are 3 semaphores
one for each of the lines: readers, writers, entry
There are 4 counters: counters for readers and writers in wait, and in use.

uSemaphore entry(1), rwait(0), wwait(0); // split binary semaphores
int rdel = 0, wdel = 0, rcnt = 0, wcnt = 0; // auxiliary counters

void Reader::main() {
  entry.P(); // pickup baton
  if ( wcnt > 0 ) { // occupied ?
    rdel += 1; entry.V(); // put baton down
    rwait.P(); rdel -= 1; // passed baton
  }
  rcnt += 1;
  if ( rdel > 0 ) { // waiting readers ?
    rwait.V(); // pass baton
  } else {
    entry.V(); // put baton down
  }
  // READ
  entry.P(); // pickup baton
  rcnt -= 1;
  if ( rcnt == 0 && wdel > 0 ) { // waiting writers ?
    wwait.V(); // pass baton
  } else {
    entry.V(); // put baton down
  }
}

void Writer::main() {
  entry.P(); // pickup baton
  if ( rcnt > 0 | | wcnt > 0 ) { // occupied ?
    wdel += 1; entry.V(); // put baton down
    wwait.P(); wdel -= 1; // passed baton
  }
  wcnt += 1;
  entry.V(); // put baton down
  // WRITE
  entry.P(); // pickup baton
  wcnt -= 1;
  if ( rdel > 0 ) { // waiting readers ?
    rwait.V(); // pass baton
  } else if ( wdel > 0 ) { // waiting writers ?
    wwait.V(); // pass baton
  } else {
    entry.V(); // put baton down
  }
}
Notice that a semaphore must be Ved in order to P on a nother semaphore.
This makes only one semaphore alive/in-use at any moment.
This solution has one problem: writer's starvation
  • because reader being in the critical section allows new readers to enter the critical section which can extend the usage time of readers as long as new readers keep on coming.
  • Solution:
    on reader's side, on acquiring the entry semaphore,
    Also wait if there is a waiting write.
    // reader
    entry.P();
    if ( wcnt > 0 | | wdel > 0 ) {
    ...
  • This will block any new readers from entering if there is a waiting write.
  • Readers wakes Writers prior to Readers
  • Writers wakes Readers prior to Writers
  • Thus, Readers done processing will wake Writers, and Writers done processing will wake Readers.
  • But this solution has a temporal barging
  • 12:30 writer leaves at 2:30
  • 1:00, 2:00 reader enters, reads the value from 12:30 writer
  • 1:30 writer enters and modifies the value.
  • The problem: 2:00 reader should have read 1:30 writer's value
    This is called staleness/freshness
    To resolve the staleness issue, read and write queue into a single queue.
    But to do this, we need some way of telling the kind of the task that will be woken up next.
    Solution 1: use a shadow queue to keep track of arriving tasks instead of having waiting counters.
    solution 2: introduce a special chair for writer. Reader wakes up next line in lock. if the woken task is a reader, repeat; else, put woken writer on the chair. any task that exits critical section must check the chair first.

    But there exists a case where schedules are out of order when you V on entry semaphore than P for waiting semaphore.
    To resolve this, you need some way of automically V and P on semaphores.
    uC++ has this:
    semaA.P(semaB); // P on semaA while V on semaB