Boost logo

Boost :

From: Alexander Terekhov (terekhov_at_[hidden])
Date: 2001-07-04 03:55:02


> > http://www.acm.org/classics/feb96/
> >
> > "Monitors: An Operating System Structuring Concept
> > C.A.R Hoare..."
>
> Thanks. This seems to be another case of me reinventing the wheel. :-)
> What he's describing there seems to be the concept I put in my thread
> library under the name Flag.

i do not think so.. condition variables are stateless and
they are working together with mutex providing atomic
unlock_and_wait semantics (with respect to other threads
and CV/mutex).

your Flag is actually an event (either auto-reset or manual):

>the current state of my threads library can be found at:
>http://storm.net.nz/~ross/c++/rs_threads.tgz

  Flag::Flag(bool broadcast):
    imp_(new Imp_) {
      imp_->event = CreateEvent(0, broadcast, false, 0);
      if (imp_->event == 0)
        throw Error("Failed to create flag\n" + windows_error_message());
    }

which was probably "invented" by Microsoft ;)

note that auto-reset event is simply a binary semaphore.
manual-reset events also have state. it is really difficult
to use events due to all sorts of race conditions with
respect to "program" state and event state. consider
the following ReadWrite lock code from your library:

  /*private*/ struct ReadWrite::Imp_ {
    HANDLE free; // Signalled while no locks held
    CRITICAL_SECTION mutex;
    long readers; // -1 if write-locked
  };

  ReadWrite::ReadWrite():
    imp_(new Imp_) {
      imp_->readers = 0;
      imp_->free = CreateEvent(0, true, false, 0);
      if (imp_->free == 0)
        throw Error("Failed to create read/write lock\n" +
windows_error_message());
      InitializeCriticalSection(&imp_->mutex);
    }

  void ReadWrite::read_lock() {
    while (!try_read_lock())
      if (WaitForSingleObject(imp_->free, INFINITE) == WAIT_FAILED)
        throw Error("Failed to acquire read lock\n" + windows_error_message
());
  }

  bool ReadWrite::try_read_lock() {
    EnterCriticalSection(&imp_->mutex);
    bool ok(imp_->readers >= 0);
    int rc(1);
    if (ok) {
      ++imp_->readers;
      rc = ResetEvent(imp_->free);
    }
    LeaveCriticalSection(&imp_->mutex);
    if (rc == 0)
      throw Error("Failed to acquire read lock\n" + windows_error_message
());
    return ok;
  }

  void ReadWrite::unlock() {
    EnterCriticalSection(&imp_->mutex);
    if (imp_->readers > 0)
      --imp_->readers;
    else
      ++imp_->readers;
    int rc(1);
    if (imp_->readers == 0)
      rc = SetEvent(imp_->free);
    LeaveCriticalSection(&imp_->mutex);
    if (rc == 0)
      throw Error("Failed to release read/write lock\n" +
windows_error_message());
  }

in the situation when

a) writer holds the lock
b) one reader is blocked on event
c) N other readers were preempted after try_read_lock
   and before WaitForSingleObject(imp_->free

when writer unlocks, he will set event which will unblock
reader b). however, since that reader will "immediately"
reset event, all c) readers could end up blocked on event
until reader b) unlocks. with all excessive synchronisation
you have (a lot of synch/set/reset calls) your event based
ReadWrite lock could fail to provide read concurrency,
which is only one problem among others..

regards,
alexander.

ps. consider the following algorithm without CVs,
and with "no read/write preference" policy; pseudo-code):

---------- Algorithm 1a / IMPL_SEM ----------

given:
mtxEntryLock - mutex or CS (or bin.semaphore/auto-reset event)
mtxReadExitLock - mutex or CS (or bin.semaphore/auto-reset event)
semWriteAccessGranted - bin.semaphore (or auto-reset event)
nReadEntryCount - int
nReadExitCount - int
nReadTriedCount - int
nWriteAccessCount - int

rdlock() {

  [auto: register int result] // error checking omitted
                                  // code for recursive readlocks omitted
  lock( mtxEntryLock );

  if ( INT_MAX == ++nReadEntryCount ) {
    lock( mtxReadExitLock );
    nReadEntryCount -= nReadExitCount;
    nReadExitCount = 0;
    unlock( mtxReadExitLock );
  }

  unlock( mtxEntryLock );

}

tryrdlock() {

  [auto: register int result] // error checking omitted
                                  // code for recursive readlocks omitted

  lock( mtxReadExitLock );

  //!!! RACE CONDITION WITH RESPECT TO nWriteAccessCount
  //!!! harmless under Win32 (atomic int store/load)
  if ( nWriteAccessCount > 0 || // writer holds the rwlock
       nReadExitCount < 0 ) { // writer blocked on rwlock
    unlock( mtxReadExitLock );
    return EBUSY;
  }

  nReadTriedCount++;

  unlock( mtxReadExitLock );

}

wrlock() {

  [auto: register int result] // error checking omitted

  lock( mtxEntryLock );

  if ( 0 == nWriteAccessCount ) {

    lock( mtxReadExitLock );

    if ( 0 != nReadExitCount ) {
      nReadEntryCount -= nReadExitCount;
      nReadExitCount = 0;
    }

    if ( 0 != nReadTriedCount ) {
      nReadEntryCount += nReadTriedCount;
      nReadTriedCount = 0;
    }

    if ( 0 != nReadEntryCount ) {

      nReadExitCount = -nReadEntryCount;
      nReadEntryCount = 0;
      unlock( mtxReadExitLock )
      sem_wait( semWriteAccessGranted );

    }
    else {
      nWriteAccessCount = 1;
      unlock( mtxReadExitLock )
    }

  }
  else { // recursive write locking
    nWriteAccessCount++; // via recursive mtxEntryLock
  }
}

trywrlock() {

  [auto: register int result] // error checking omitted

  if ( EBUSY == trylock( mtxEntryLock ) ) {
    return EBUSY;
  }

  if ( 0 != nWriteAccessCount ) {
    unlock( mtxEntryLock );
    return EBUSY;
  }

  lock( mtxReadExitLock );

  if ( 0 != nReadExitCount ) {
    nReadEntryCount -= nReadExitCount;
    nReadExitCount = 0;
  }

  if ( 0 != nReadTriedCount ) {
    nReadEntryCount += nReadTriedCount;
    nReadTriedCount = 0;
  }

  if ( 0 != nReadEntryCount ) {
    unlock( mtxReadExitLock );
    unlock( mtxEntryLock );
    return EBUSY;
  }

  nWriteAccessCount = 1;

  unlock( mtxReadExitLock );
}

unlock() {

  [auto: register int result ] // error checking omitted
  [auto: register int bGrantWriteAccess]
                                         // code for recursive readlocks
omitted

  if ( 0 == nWriteAccessCount ) {
    lock( mtxReadExitLock );
    if ( 0 == nReadTriedCount ) {
      if ( 0 == ++nReadExitCount ) {
        nWriteAccessCount = 1;
        bGrantWriteAccess = true;
      }
      else {
        bGrantWriteAccess = false;
      }
    }
    else {
      nReadTriedCount--;
      bGrantWriteAccess = false;
    }
    unlock( mtxReadExitLock );
    if ( bGrantWriteAccess ) {
      sem_post( semWriteAccessGranted );
    }
  }
  else {
    nWriteAccessCount--;
    unlock( mtxEntryLock );
  }
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk