Homepage Demos Overview Downloads Tutorials Reference
Credits
Main Page | Namespace List | Class Hierarchy | Alphabetical List | Compound List | File List | Namespace Members | Compound Members | File Members | Related Pages | Search

MutexLock.h

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #ifndef __MUTEX_LOCK_ET__
00003 #define __MUTEX_LOCK_ET__
00004 
00005 #include "debuget.h"
00006 #include <iostream>
00007 
00008 //#define MUTEX_LOCK_ET_USE_SPINCOUNT
00009 
00010 //! A software only mutual exclusion lock.
00011 /*! Use this to prevent more than one process from accessing a data structure
00012  *  at the same time (which often leads to unpredictable and unexpected results)
00013  *
00014  *  The template parameter specifies the maximum number of different processes
00015  *  which need to be protected.  This needs to be allocated ahead of time, as
00016  *  there doesn't seem to be a way to dynamically scale as needed without
00017  *  risking possible errors if two processes are both trying to set up at the
00018  *  same time.  Also, by using a template parameter, all data structures are
00019  *  contained within the class's memory allocation, so no pointers are involved.
00020  *
00021  *  Locks in this class can be recursive.  If you lock 5 times and then unlock()
00022  *  once, the lock is released.  If you want to have releases match locks, call
00023  *  release() instead of unlock().
00024  *
00025  *  Note that there is no check that the process doing the unlocking is the one
00026  *  that actually has the lock.  Be careful about this.
00027  *
00028  *  @warning Doing mutual exclusion in software is tricky business, be careful about any
00029  *  modifications you make!
00030  *
00031  * Implements a first-come-first-served Mutex as laid out on page 11 of: \n
00032  * "A First Come First Served Mutal Exclusion Algorithm with Small Communication Variables" \n
00033  * Edward A. Lycklama, Vassos Hadzilacos - Aug. 1991
00034 */
00035 template<unsigned int num_doors>
00036 class MutexLock {
00037  public:
00038   static const unsigned int NO_OWNER=-1U; //!< marks as unlocked
00039 
00040   //! constructor, just calls the init() function.
00041   MutexLock() : doors_used(0), owner_index(NO_OWNER), lockcount(0) { init();  }
00042 
00043   //! blocks (by busy looping on do_try_lock()) until a lock is achieved
00044   /*! You should pass some process-specific ID number as the input - just
00045    *  make sure no other process will be using the same value.
00046    *
00047    *  This is not a recursive lock - repeated locks still only require one
00048    *  release to undo them.
00049    *  @todo - I'd like to not use a loop here */
00050   void lock(int id);
00051 
00052   //! attempts to get a lock, returns true if it succeeds
00053   /*! You should pass some process-specific ID number as the input - just
00054    *  make sure no other process will be using the same value.
00055    *
00056    *  This is not a recursive lock - repeated locks still only require one
00057    *  release to undo them. */
00058   bool try_lock(int id);
00059 
00060   //! releases one recursive lock-level from whoever has the current lock
00061   void release();
00062 
00063   //! completely unlocks, regardless of how many times a recursive lock has been obtained
00064   inline void unlock() { lockcount=1; release(); }
00065 
00066   //! returns the lockcount
00067   unsigned int get_lock_level() const { return lockcount; }
00068 
00069   //! returns the current owner's id
00070   inline int owner() { return owner_index==NO_OWNER ? NO_OWNER : doors[owner_index].id; }
00071 
00072   //! allows you to reset one of the possible owners, so another process can take its place.  This is not tested
00073   void forget(int id);
00074 
00075 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00076   inline unsigned int getSpincount() { return spincount; } //!< returns the number of times the spin() function has been called
00077   inline unsigned int resetSpincount() { spincount=0; } //!< resets the counter of the number of times the spin() function has been called
00078 #endif
00079   
00080  protected:
00081   //! Does the work of trying to get a lock
00082   /*! Pass @c true for @a block if you want it to use FCFS blocking
00083    *  instead of just returning right away if another process has the lock */
00084   bool do_try_lock(unsigned int index, bool block);
00085 
00086   //! returns the internal index mapping to the id number supplied by the process
00087   unsigned int lookup(int id); //may create a new entry
00088 
00089 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00090   volatile unsigned int spincount; //!< handy to track how much time we're wasting
00091   void init() { spincount=0; }//memset((void*)doors,0,sizeof(doors)); } //!< just resets spincount
00092   inline void spin() { spincount++; } //!< if you find a way to sleep for a few microseconds instead of busy waiting, put it here
00093 #else
00094   void init() { } //!< Doesn't do anything if you have the MUTEX_LOCK_ET_USE_SPINCOUNT undef'ed.  Used to do a memset, but that was causing problems....
00095   //memset((void*)doors,0,sizeof(doors)); } 
00096   inline void spin() {} //!< If you find a way to sleep for a few microseconds instead of busy waiting, put it here
00097 #endif
00098     
00099   //! Holds per process shared info, one of these per process
00100   struct door_t {
00101     door_t() : id(NO_OWNER), FCFS_in_use(false), BL_ready(false), BL_in_use(false), turn('\0'), next_turn_bit('\0') {} //!< constructor
00102     //door_t(int i) : id(i), FCFS_in_use(false), BL_ready(false), BL_in_use(false), next_turn_bit('\0') {}
00103     int id; //!< process ID this doorway is assigned to
00104     volatile bool FCFS_in_use; //!< In FCFS doorway, corresponds to 'c_i'
00105     volatile bool BL_ready; //!< Signals past FCFS doorway, ready for BL doorway, corresponds to 'v_i'
00106     volatile bool BL_in_use; //!< Burns-Lamport doorway, corresponds to 'x_i'
00107     volatile unsigned char turn; //!< clock pulse, initial value doesn't matter
00108     unsigned char next_turn_bit; //!< selects which bit of turn will be flipped next
00109   };
00110 
00111   door_t doors[num_doors]; //!< holds all the doors
00112   unsigned int doors_used; //!< counts the number of doors used
00113   unsigned int owner_index; //!< holds the door index of the current lock owner
00114   unsigned int lockcount; //!< the depth of the lock, 0 when unlocked
00115 };
00116 
00117 
00118 template<unsigned int num_doors>
00119 void
00120 MutexLock<num_doors>::lock(int id) {
00121   if(owner()!=id)
00122     while(!do_try_lock(lookup(id),true))
00123       spin();
00124   lockcount++;
00125 }
00126 
00127 
00128 template<unsigned int num_doors>
00129 bool
00130 MutexLock<num_doors>::try_lock(int id) {
00131   if(owner()==id) {
00132     lockcount++;
00133     return true;
00134   } else {
00135     if(do_try_lock(lookup(id),false)) {
00136       lockcount++;
00137       return true;
00138     } else
00139       return false;
00140   }
00141 }
00142 
00143 
00144 template<unsigned int num_doors>
00145 void
00146 MutexLock<num_doors>::release() {
00147   if(lockcount>0)
00148     if(--lockcount==0)
00149       if(owner_index!=NO_OWNER) {
00150         unsigned int tmp = owner_index;
00151         owner_index=NO_OWNER;
00152         doors[tmp].BL_in_use=false;
00153         doors[tmp].BL_ready=false;
00154         // *** Lock has been released *** //
00155       }
00156 }
00157 
00158 
00159 //! If you define this to do something more interesting, can use it to see what's going on in the locking process
00160 #define mutexdebugout(i,c) {}
00161 //#define mutexdebugout(i,c) { std::cout << ((char)(i==0?c:((i==1?'M':'a')+(c-'A')))) << std::flush; }
00162 
00163 
00164 template<unsigned int num_doors>
00165 bool
00166 MutexLock<num_doors>::do_try_lock(unsigned int i, bool block) {
00167   if(i==NO_OWNER) {
00168     std::cerr << "WARNING: new process attempted to lock beyond num_doors ("<<num_doors<<")" << std::endl;
00169     return false;
00170   }
00171   unsigned char S[num_doors]; // a local copy of everyone's doors
00172   // *** Entering FCFS doorway *** //
00173 //  pprintf(TextOutputStream,"**%d**\n",i);
00174 mutexdebugout(i,'A');
00175   doors[i].FCFS_in_use=true;
00176   for(unsigned int j=0; j<num_doors; j++)
00177     S[j]=doors[j].turn;
00178   doors[i].next_turn_bit=1-doors[i].next_turn_bit;
00179   doors[i].turn^=(1<<doors[i].next_turn_bit);
00180   doors[i].BL_ready=true;
00181   doors[i].FCFS_in_use=false;
00182   // *** Leaving FCFS doorway *** //
00183 mutexdebugout(i,'B');
00184   for(unsigned int j=0; j<num_doors; j++) {
00185 mutexdebugout(i,'C');
00186     while(doors[j].FCFS_in_use || (doors[j].BL_ready && S[j]==doors[j].turn))
00187       if(block)
00188         spin();
00189       else {
00190         doors[i].BL_ready=false;
00191         return false;
00192       }
00193 mutexdebugout(i,'D');
00194   }
00195   // *** Entering Burns-Lamport *** //
00196 mutexdebugout(i,'E');
00197   do {
00198     doors[i].BL_in_use=true;
00199     for(unsigned int t=0; t<i; t++)
00200       if(doors[t].BL_in_use) {
00201         doors[i].BL_in_use=false;
00202         if(!block) {
00203           doors[i].BL_ready=false;
00204           return false;
00205         }
00206 mutexdebugout(i,'F');
00207         while(doors[t].BL_in_use)
00208           spin();
00209 mutexdebugout(i,'G');
00210         break;
00211       }
00212   } while(!doors[i].BL_in_use);
00213   for(unsigned int t=i+1; t<num_doors; t++)
00214     while(doors[t].BL_in_use)
00215       spin();
00216   // *** Leaving Burns-Lamport ***//
00217   // *** Lock has been given *** //
00218 mutexdebugout(i,'H');
00219   owner_index=i;
00220   return true;
00221 }
00222 
00223 
00224 template<unsigned int num_doors>
00225 unsigned int
00226 MutexLock<num_doors>::lookup(int id) {
00227   // TODO - this could break if two new processes are adding themselves at the same time
00228   //        or an id is being forgotten at the same time
00229   //I'm expecting a very small number of processes to be involved
00230   //probably not worth overhead of doing something fancy like a sorted array
00231   unsigned int i;
00232   for(i=0; i<doors_used; i++)
00233     if(doors[i].id==id)
00234       return i;
00235   if(i==num_doors)
00236     return NO_OWNER;
00237   doors[i].id=id;
00238   doors_used++;
00239   return i;
00240 }
00241 
00242 
00243 template<unsigned int num_doors>
00244 void
00245 MutexLock<num_doors>::forget(int id) { //not tested thoroughly
00246   unsigned int i = lookup(id);
00247   do_try_lock(i,true);
00248   doors[i].id=doors[--doors_used].id;
00249   doors[doors_used].id=NO_OWNER;
00250   release();
00251 }
00252 
00253 
00254 /*! @file 
00255  * @brief Defines MutexLock, a software only mutual exclusion lock.
00256  * @author ejt (Creator), Edward A. Lycklama, Vassos Hadzilacos (paper from which this was based)
00257  *
00258  * $Author: ejt $
00259  * $Name: tekkotsu-1_4_1 $
00260  * $Revision: 1.7 $
00261  * $State: Exp $
00262  * $Date: 2003/06/12 23:41:40 $
00263  */
00264 
00265 #endif

Tekkotsu v1.4
Generated Sat Jul 19 00:06:31 2003 by Doxygen 1.3.2