Main Page | Class Hierarchy | Class List | File List | Class Members | File Members

ReadWriteLock.h

Go to the documentation of this file.
00001 /*  ReadWriteLock.h - N readers, N writers problem with preference
00002     for readers
00003 
00004     Copyright (C) 2001-2004 Mark Weaver
00005     Written by Mark Weaver <mark@npsl.co.uk>
00006 
00007     Part of the Open-Win32 library.
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public
00019     License along with this library; if not, write to the
00020     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00021     Boston, MA  02111-1307, USA.
00022 */
00023 
00028 #ifndef OW32_ReadWriteLock_h
00029 #define OW32_ReadWriteLock_h
00030 
00031 #include <OW32/SyncObjects.h>
00032 #include <cstddef>
00033 #include <assert.h>
00034 
00035 namespace OW32
00036 {
00037 
00052 class OW32_LIB_EXPORT CReadWriteLock
00053 {
00054 public:
00058     CReadWriteLock() :
00059         m_activeReaders(0), 
00060         m_writerID(0), 
00061         m_writerRecursionCount(0),
00062         m_pendingReaders(0), 
00063         m_pendingWriters(0),
00064         m_freeToRead(TRUE)
00065     {
00066     }
00067 
00073     CSyncObject& GetWriteLock()
00074     {
00075         return m_writeLock;
00076     }
00077 
00083     CSyncObject& GetReadLock()
00084     {
00085         return m_readLock;
00086     }
00087 
00088 private:
00095     BOOL readLock(DWORD timeout)
00096     {
00097         DWORD dwStart = GetTickCount();
00098         DWORD pendIncrement = 1;
00099         DWORD myID = ::GetCurrentThreadId();
00100         for (;;)
00101         {
00102             bool needReset = false;
00103 
00104             { // Lock scope
00105                 CSingleLock lock(m_stateLock);
00106 
00107                 // Too many pending or active? (caps too early, but not really relevant...)
00108                 if (m_pendingReaders == 0xffffffff || m_activeReaders == 0xffffffff)
00109                 {
00110                     ::SetLastError(ERROR_BUSY);
00111                     return FALSE;
00112                 }
00113 
00114                 // Am I a writer? If so, just recurse the write lock.
00115                 // (Note that there is no upgrade so you can't be a reader already:
00116                 // upgrade would require a container of thread IDs).
00117                 if (m_writerRecursionCount > 0 && m_writerID == myID)
00118                 {
00119                     ++m_writerRecursionCount;
00120                     return TRUE;
00121                 }
00122 
00123                 m_pendingReaders += pendIncrement;
00124                 pendIncrement = 0;
00125                 // Not write locked, and no waiting writers
00126                 if (m_writerRecursionCount == 0 && m_pendingWriters == 0)
00127                 {
00128                     //OWTRACE(_T("%lu: got read lock\n"), ::GetCurrentThreadId());
00129                     ++m_activeReaders;
00130 
00131                     // Seems odd, but we can only reset this event when no-one is
00132                     // waiting on it (otherwise the lost signal would be a disaster)
00133                     // or we know that we will generate the signal
00134                     // When there are no pending readers then no-one is waiting below
00135                     // We also optimise by only resetting if anyone actually waited
00136                     // on the event
00137                     if (--m_pendingReaders == 0 && needReset)
00138                     {
00139                         m_freeToRead.Reset();
00140                     }
00141                     return TRUE;
00142                 }
00143             }
00144 
00145             // wait for `write complete' signal
00146             DWORD timeoutRemaining = INFINITE;
00147             if (timeout != INFINITE)
00148             {   
00149                 timeoutRemaining = GetTickCount() - dwStart;
00150                 if (timeoutRemaining > timeout)
00151                     timeoutRemaining = 0;
00152 #if 0
00153                 printf("sleep for %lu\n", timeoutRemaining);
00154 #endif
00155             }
00156             if (!m_freeToRead.Lock(timeoutRemaining))
00157                 break;
00158             needReset = true;
00159         }
00160         // Timed out, remove us from pending readers
00161         if (pendIncrement == 0)
00162         {
00163             CSingleLock lock(m_stateLock);
00164 
00165             // Reset the event as above.  We waited on it, so no need for
00166             // the conditional check.
00167             if (--m_pendingReaders == 0)
00168             {
00169                 m_freeToRead.Reset();
00170             }
00171         }
00172         ::SetLastError(ERROR_TIMEOUT);
00173         return FALSE;
00174     }
00175 
00179     void readUnlock()
00180     {
00181         CSingleLock lock(m_stateLock);
00182 
00183         // Am I really a writer?
00184         if (m_writerRecursionCount > 0)
00185         {
00186             writeUnlockLocked();
00187             return;
00188         }
00189 
00190         // Can't have any writers, must have a reader
00191         assert(m_writerRecursionCount == 0);
00192         assert(m_activeReaders > 0);
00193         // Ditch one reader
00194         --m_activeReaders;
00195         if (m_activeReaders == 0 && m_pendingWriters > 0) 
00196         {
00197             // Release one waiting writer
00198             m_freeToWrite.Set();
00199         }
00200         //OWTRACE(_T("%lu: readUnlock\n"), ::GetCurrentThreadId());
00201     }
00202 
00206     class OW32_LIB_EXPORT CReadLock : public CSyncObject
00207     {
00208     private:
00210         CReadLock(const CReadLock&);
00212         CReadLock& operator=(const CReadLock&);
00213 
00214     public:
00218         CReadLock()
00219         {
00220         }
00221 
00228         virtual BOOL Lock(DWORD timeout)
00229         {
00230             CReadWriteLock& parent = *(CReadWriteLock *)((const char *)this-offsetof(CReadWriteLock, m_readLock));
00231             return parent.readLock(timeout);
00232         }
00233 
00237         virtual void Unlock()
00238         {
00239             CReadWriteLock& parent = *(CReadWriteLock *)((const char *)this-offsetof(CReadWriteLock, m_readLock));
00240             parent.readUnlock();
00241         }
00242     };
00243     friend CReadLock;
00244 
00251     BOOL writeLock(DWORD timeout)
00252     {
00253         DWORD dwStart = GetTickCount();
00254         DWORD pendIncrement = 1;
00255         DWORD myID = GetCurrentThreadId();
00256         for (;;)
00257         {
00258             { // Lock scope
00259                 CSingleLock lock(m_stateLock);
00260 
00261                 // Too many pending (caps too early, maybe, but not really relevant...)
00262                 if (m_pendingWriters == 0xffffffff)
00263                 {
00264                     ::SetLastError(ERROR_BUSY);
00265                     return FALSE;
00266                 }
00267 
00268                 m_pendingWriters += pendIncrement;
00269                 pendIncrement = 0;
00270 
00271                 // No readers?
00272                 if (m_activeReaders == 0)
00273                 {
00274                     // No active writers or me again
00275                     if (m_writerRecursionCount == 0 || m_writerID == myID)
00276                     {
00277                         // Grab the lock
00278                         --m_pendingWriters;
00279                         m_writerID = myID;
00280                         ++m_writerRecursionCount;
00281 
00282                         // Since we now have the lock, reset the 'freeToRead' event
00283                         // if there are pending readers.  Might be unnecessary, but
00284                         // it might be that it got set last time around and couldn't
00285                         // be unset by the repeated arrival of new readers.  Here
00286                         // we know if anyone pends we will set the event, so it's ok
00287                         // to clear it here.
00288                         if (m_pendingReaders > 0)
00289                             m_freeToRead.Reset();
00290                         return TRUE;
00291                     }
00292                     // Otherwise, have an active writer
00293                 }
00294             }
00295 
00296             // Wait for `write complete' signal
00297             DWORD timeoutRemaining = INFINITE;
00298             if (timeout != INFINITE)
00299             {   
00300                 timeoutRemaining = GetTickCount() - dwStart;
00301                 if (timeoutRemaining > timeout)
00302                     timeoutRemaining = 0;
00303             }
00304             // Wait until OK to write (=> no active readers or writers)
00305             if (!m_freeToWrite.Lock(timeoutRemaining))
00306                 break;
00307         }
00308         // Timeout
00309         //OWTRACE(_T("%lu: writeLock timeout\n"), ::GetCurrentThreadId());
00310         if (pendIncrement == 0) 
00311         {
00312             CSingleLock lock(m_stateLock);
00313             if (--m_pendingWriters == 0 && m_pendingReaders > 0)
00314             {
00315                 // Release a possibly blocked pending reader
00316                 m_freeToRead.Set();
00317             }
00318         }
00319         ::SetLastError(ERROR_TIMEOUT);
00320         return FALSE;
00321     }
00322 
00326     void writeUnlock()
00327     {
00328         CSingleLock lock(m_stateLock);
00329         
00330         writeUnlockLocked();
00331         //OWTRACE(_T("%lu: writeUnlock\n"), ::GetCurrentThreadId());
00332     }
00333 
00340     void writeUnlockLocked()
00341     {
00342         // Check state
00343         assert(m_writerID == GetCurrentThreadId());
00344         assert(m_writerRecursionCount > 0);
00345         assert(m_activeReaders == 0);
00346 
00347         // Recurse out
00348         if (--m_writerRecursionCount == 0)
00349         {
00350             // Signal write completion with writer preference
00351             if (m_pendingWriters != 0)
00352             {
00353                 //OWTRACE(_T("%lu: writeUnlock, release writer\n"), ::GetCurrentThreadId());
00354                 m_freeToWrite.Set();
00355             }
00356             else if (m_pendingReaders != 0)
00357             {
00358                 //OWTRACE(_T("%lu: writeUnlock, release reader\n"), ::GetCurrentThreadId());
00359                 m_freeToRead.Set();
00360             }
00361         }
00362     }
00363 
00367     class OW32_LIB_EXPORT CWriteLock : public CSyncObject
00368     {
00369     private:
00371         CWriteLock(const CWriteLock&);
00373         CWriteLock& operator=(const CWriteLock&);
00374 
00375     public:
00379         CWriteLock()
00380         {
00381         }
00382 
00389         virtual BOOL Lock(DWORD timeout)
00390         {
00391             CReadWriteLock& parent = *(CReadWriteLock *)((const char *)this-offsetof(CReadWriteLock, m_writeLock));
00392             return parent.writeLock(timeout);
00393         }
00394 
00398         virtual void Unlock()
00399         {
00400             CReadWriteLock& parent = *(CReadWriteLock *)((const char *)this-offsetof(CReadWriteLock, m_writeLock));
00401             parent.writeUnlock();
00402         }
00403     };
00404     friend CWriteLock;
00405 
00407     CReadWriteLock(const CReadWriteLock&);
00409     CReadWriteLock& operator=(const CReadWriteLock&);
00410 
00412     CCriticalSection    m_stateLock;
00414     DWORD               m_pendingReaders;
00416     DWORD               m_activeReaders;
00418     DWORD               m_pendingWriters;
00420     DWORD               m_writerID;
00422     DWORD               m_writerRecursionCount;
00424     CEvent              m_freeToWrite;
00426     CEvent              m_freeToRead;
00427 
00429     CReadLock           m_readLock;
00431     CWriteLock          m_writeLock;
00432 };
00433 
00434 #ifdef _MSC_VER
00435 #pragma warning(default: 4251)
00436 #endif
00437 
00438 } // namespace OW32
00439 
00440 #endif // OW32_ReadWriteLock_h

Generated on Sun Jun 5 01:29:18 2005 for OW32 by  doxygen 1.3.9.1