[The attachment got messed up in the last post.  Hopefully, it will be OK
this time]

This is my final repost of my Lock Classes.  If I do not get any
constructive feedback this time I am going to give up.

These classes have the following features:

  1) The ability to acquire a lock and release it when the object
     goes out of scope effectively implementing the "Monitor" concept.
  
  2) Avoid the need for recursive locks by careful and efficient
     book keeping with the out the use of global thread specific
     data.
  
  3) Document and enforce guaranteed lock behavior for functions
     which take a lock class as a parameter.

Recursive locks may avoid the need for the bookkeeping.  However recursive
looks are more expensive.  Also, with recursive locks, as far as I know,
it is imposable to temporally release the lock.  For example when calling
a network function I don't want to keep the lock as the object is in a
consistent state and holding the lock while waiting for a reply will
greatly reduce concurrency.  My lock classes makes this possible to do
efficiently and safely as it is not possible to accentually call a
function that will release a lock without giving it permission to do so.

The only downside is that you have to explicit pass the lock around to
each function as a parameter.  My recommendation is to use the lock
classes as parameters to private methods or functions which may call each
other and thus need to avoid acquiring a non recursive lock more than
once.  Public members can than call the private members by simply 
providing the function with the appropriate lock.

I have not seen anything quite like it so I thought I would share it here 
and see what other people think. 

Feedback on the idea or implementation welcome.  This code, at the moment, 
does not follow boost standards.  If people think it is a worthy addition 
to boost I will be willing to being it up to boost standards.  But for 
right now please refrain from making comments on coding style or the 
like.

---
http://kevin.atkinson.dhs.org
// File: lock.hpp
//
// Copyright (c) 2002,2003 
// Kevin Atkinson
//
// Permission to use, copy, modify, distribute and sell this software
// and its documentation for any purpose is hereby granted without
// fee, provided that the above copyright notice appear in all copies
// and that both that copyright notice and this permission notice
// appear in supporting documentation.  Kevin Atkinson makes no
// representations about the suitability of this software for any
// purpose.  It is provided "as is" without express or implied
// warranty.

#ifndef DISTRIBNET_LOCK__HPP
#define DISTRIBNET_LOCK__HPP

namespace distribnet {

  // These lock classes serve three functions:
  //
  // 1) The ability to aquire a lock and release it when the object
  //    goes out of scope effectvly implemented the "Monitor" concept.
  //
  // 2) Avoid the need for recursive locks by careful and efficient
  //    book keeping with the out the use of global thread specific
  //    data.
  //
  // 3) Document and enforce guaranteed lock behavior for functions
  //    which take a lock class as a parameter.
  //

  // The global lock used to initialize the lock classes.
  class Mutex;

  // The scoped lock classes:

  class LockOnly;   // If the lock is not already aquire it will do so
                    // and then release it at the end of the scope.
                    // It will not release it under any other
                    // circumstances.

  class Lock;       // Will aquire the lock if necessary but may also
                    // release it at any time.  Do not call a function
                    // with this type of lock while in an inconsistent
                    // state.

  class UnlockOnly; // If the lock is already aquired it will be
                    // released and then reaquire at the end of the
                    // scope.  If will not aquire a lock under any
                    // other circumstances.

  class Unlock;     // If locked is it will be released it, however
                    // it may reaquire at any time.  Do not call a
                    // function with this type of lock if it is
                    // unexceptable to wait for the lock to be
                    // reaquired.

  // These locks should never be passed by value to functions.
  // Instead use the following typedef to pass by reference:
  typedef const LockOnly   & WillOnlyLock;
  typedef const Lock       & WillLock;
  typedef const UnlockOnly & WillOnlyUnlock;
  typedef const Unlock     & WillUnlock;

  // The following lock "states" are also provided.  When a functions
  // takes one of these lock states as a parameter it may change the
  // lock state at any time but then again it might not.

  class MightLockUnlock; // Might aquire or release the lock or both.
  typedef MightLockUnlock LockState; // alternate name

  class MightLock;       // Might aquire a lock but will not release it
                         // if the lock is already aquired.

  class MightUnlock;     // Might release the lock but will not aquire one
                         // if the lock is already released.

  // Lock states may be either passed by const refrence or by value.

  // The various lock and lock states may be converted between each
  // other as follows.  (All relationships are tranasitive).

  //             <== Lock <=> MightLockUnlock <=> Unlock ==>
  // MightLock <=> LockOnly                     UnlockOnly <=> MightLock

  // Never pass any of the locks or the lock states by non const
  // refrence as that will mess up the automatic conversion between
  // the lock types.

  // All Lock classes and states have a public constructor with the
  // following parms, (Mutex &, bool locked = false).  The first
  // paramter is the global mutex which must be provided.  The second 
  // paramter is the state of the mutex which defaults to unlocked.

  // To lock a region of code use these macros like so:
  //   {LOCK(l); /*...*/}
  // Or without using these macros
  //   {Lock l(l); /*...*/}
  // However do NOT do this
  //   {Lock l0(l); /*...*/}
  // as this is can easily lead to mistakes such as
  //   void f(Unlock l);
  //   {Lock l0(l); ...; f(l /* should of been l0 */);}
  // which is wrong and will cause a variaty of problems from not
  // locking/unlocking a region of code that should be to locking a
  // already locked mutex or unlocking a mutex which the current
  // thread does not own.  The latter two cases will likely cause
  // deadlock unless an error checking mutex is used.
  
#ifndef __GNUC__
#define __attribute__(x)
#endif

#define LOCK(l) const Lock l(l) __attribute__((__unused__))
#define UNLOCK(l) const Unlock l(l) __attribute__((__unused__))
#define LOCK_ONLY(l) const LockOnly l(l) __attribute__((__unused__))
#define UNLOCK_ONLY(l) const UnlockOnly l(l) __attribute__((__unused__))
#define LOCKO LOCK_ONLY
#define UNLOCKO UNLOCK_ONLY

  //////////////////////////////////////////////////////////////////////
  //
  // The implementation
  //

  class Mutex {
    // implementation unimportant
  private:
    Mutex(const Mutex &);
    void operator=(const Mutex &);
  public:
    Mutex() {}
    void lock() {}
    void unlock() {}
  };

  class LockBase;
  class UnlockBase;

  class MightBase {
    friend class LockBase;
    friend class UnlockBase;
  protected:
    Mutex & lock_;
    inline MightBase(const LockBase &);
    inline MightBase(const UnlockBase &);
    MightBase(Mutex & l, bool lkd) : lock_(l), locked(lkd) {}
  public:
    const bool locked;
  };

  class MightLockUnlock : public MightBase {
  public:
    MightLockUnlock(Mutex & l, bool lkd = false) : MightBase(l, lkd) {}
    inline MightLockUnlock(const Lock & l);
    inline MightLockUnlock(const Unlock & l);
  };

  class MightLock : public MightBase {
  public:
    MightLock(Mutex & l, bool lkd = false) : MightBase(l, lkd) {}
    MightLock(const MightLockUnlock & l) : MightBase(l) {}
    inline MightLock(const LockOnly & l);
    inline MightLock(const Unlock & l);
  };

  class MightUnlock : public MightBase {
  public:
    MightUnlock(Mutex & l, bool lkd = false) : MightBase(l, lkd) {}
    MightUnlock(const MightLockUnlock & l) : MightBase(l) {}
    inline MightUnlock(const UnlockOnly &);
    inline MightUnlock(const Lock &);
  };

  class LockBase {
    friend class MightBase;
  private:
    LockBase(const LockBase &);
    void operator= (const LockBase &);
  protected:
    Mutex & lock_;
    const bool this_scope;
    LockBase(Mutex & l, bool lkd = false) 
      : lock_(l), this_scope(!lkd)
      {if (this_scope) lock_.lock();}
    LockBase(const MightBase & l) 
      : lock_(l.lock_), this_scope(!l.locked)
      {if (this_scope) lock_.lock();}
    ~LockBase() {if (this_scope) lock_.unlock();}
  };

  class LockOnly : public LockBase {
  public:
    LockOnly(Mutex & l, bool lkd = false) : LockBase(l, lkd) {}
    LockOnly(const MightLock & l) : LockBase(l) {}
    LockOnly(const MightLockUnlock & l) : LockBase (l) {}
    inline LockOnly(const Unlock &);
  };

  class Lock : public LockOnly
  {
    friend class UnlockOnly;
  public:
    Lock(Mutex & l, bool lkd = false) : LockOnly(l, lkd) {}
    Lock(const MightLockUnlock & l) : LockOnly(l) {}
    Lock(const Unlock & l) : LockOnly(l) {}
  };

  class UnlockBase {
    friend class MightBase;
  private:
    UnlockBase(const UnlockBase &);
    void operator= (const UnlockBase &);
  protected:
    Mutex & lock_;
    const bool this_scope;
    UnlockBase(Mutex & l, bool lkd = false) 
      : lock_(l), this_scope(lkd)
      {if (this_scope) lock_.unlock();}
    UnlockBase(const MightBase & l) 
      : lock_(l.lock_), this_scope(l.locked)
      {if (this_scope) lock_.unlock();}
    ~UnlockBase() {if (this_scope) lock_.lock();}
  };

  class UnlockOnly : public UnlockBase {
  public:
    UnlockOnly(Mutex & l, bool lkd = false) : UnlockBase(l, lkd) {}
    UnlockOnly(const MightUnlock & l) : UnlockBase(l) {}
    UnlockOnly(const MightLockUnlock & l) : UnlockBase (l) {}
    inline UnlockOnly(const Lock &); 
  };

  class Unlock : public UnlockOnly
  {
    friend class LockOnly;
  public:
    Unlock(Mutex & l, bool lkd = false) : UnlockOnly(l, lkd) {}
    Unlock(const MightLockUnlock & l) : UnlockOnly(l) {}
    Unlock(const Lock & l) : UnlockOnly(l) {}
  };

  inline MightBase::MightBase(const LockBase & l)
    : lock_(l.lock_), locked(true) {}
  inline MightBase::MightBase(const UnlockBase & l)
    : lock_(l.lock_), locked(false) {}

  inline MightLockUnlock::MightLockUnlock(const Lock & l) 
    : MightBase(l) {}
  inline MightLockUnlock::MightLockUnlock(const Unlock & l) 
    : MightBase(l) {}

  inline MightLock::MightLock(const LockOnly & l) 
    : MightBase(l) {}
  inline MightLock::MightLock(const Unlock & l) 
    : MightBase(l) {}

  inline MightUnlock::MightUnlock(const UnlockOnly & l) 
    : MightBase(l) {}
  inline MightUnlock::MightUnlock(const Lock & l) 
    : MightBase(l) {}

  inline LockOnly::LockOnly(const Unlock & l)
    : LockBase(l.lock_, false) {}

  inline UnlockOnly::UnlockOnly(const Lock & l)
    : UnlockBase(l.lock_, true) {}

}

#endif
// File: example.cpp
//
// Copyright (c) 2003 
// Kevin Atkinson
//
// Permission to use, copy, modify, distribute and sell this software
// and its documentation for any purpose is hereby granted without
// fee, provided that the above copyright notice appear in all copies
// and that both that copyright notice and this permission notice
// appear in supporting documentation.  Kevin Atkinson makes no
// representations about the suitability of this software for any
// purpose.  It is provided "as is" without express or implied
// warranty.
//
// Lock Example

#include "lock.hpp"

using namespace distribnet;

class MultithreadedObject
{
public:
  void init();
  void sync();
private:
  void setup(LockState);
  void modify1(WillOnlyLock);
  void modify2(WillOnlyLock);
  void modify3(WillOnlyLock);
  void synchronize(WillLock);
  void network_function(WillUnlock);

  MultithreadedObject()
    : counter(0), var1(10), var2(20) {}
  int counter;
  int var1;
  int var2;
  Mutex lock;
};

void MultithreadedObject::init()
{
  {
    LOCK(lock); // will automaticlly unlock at the end of the scope
    counter = 0;
    var1 = var2 = 99;
    setup(lock);
  }
  // now do things that don't need the lock
}

void MultithreadedObject::sync()
{
  synchronize(lock); // simply pass in the lock.  The lock classes will
                     // take care of the rest
}


void MultithreadedObject::setup(LockState l)
// will simply pass the lock state around to other functions that may
// lock or unlock
{
  modify1(l);
  synchronize(l);
}

void MultithreadedObject::modify1(WillOnlyLock)
// If not locked will lock when the function is called and unlock
// when the function returns.  Will not unlock otherwise.
{
  var1 = 111;
}

void MultithreadedObject::modify2(WillOnlyLock)
{
  var2 = 222;
}

void MultithreadedObject::modify3(WillOnlyLock)
{
  var2 = 333;
}

void MultithreadedObject::synchronize(WillLock l)
// If not locked will lock when the function is called and unlock
// when the function returns.  But is allowed to also Unlock the
// mutex inside the function.
{
  modify2(l);
  network_function(l); // this is ok becuase I am allowed to unlock
  modify3(l);
}

void MultithreadedObject::network_function(WillUnlock)
// If locked will unlock when the function is called and lock when the
// function returns.  Also allowed to lock in the middle of the
// function.
{
  // use network functions which may block for a long time
}
_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Reply via email to