1
/*********************************************************************
2 CReaderWriterLock: A simple and fast reader-writer lock class in C++
3 has characters of .NET ReaderWriterLock class
4 Copyright (C) 2006 Quynh Nguyen Huu
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 Email questions, comments or suggestions to quynhnguyenhuu@gmail.com
17 *********************************************************************/
21 #include "ReaderWriterLock.h"
23 /////////////////////////////////////////////////////////////////
24 // Following macros make this file can be used in non-MFC project
26 // Define ASSERT macro
27 # define ASSERT _ASSERT
29 // Define VERIFY macro
31 # define VERIFY ASSERT
32 # define DEBUG_ONLY(f) ((void)(f))
34 # define VERIFY(f) ((void)(f))
35 # define DEBUG_ONLY(f)
39 ///////////////////////////////////////////////////////
40 // CReaderWriterLockNonReentrance implementation
42 CReaderWriterLockNonReentrance::CReaderWriterLockNonReentrance()
43 : m_hSafeToReadEvent(NULL
)
44 , m_hSafeToWriteEvent(NULL
)
46 , m_iNumOfReaderWaiting(0)
47 , m_iNumOfReaderEntered(0)
49 SecureZeroMemory(this, sizeof(*this));
50 #if (_WIN32_WINNT >= 0x0403)
51 InitializeCriticalSectionAndSpinCount(&m_cs
, READER_WRITER_SPIN_COUNT
);
53 InitializeCriticalSection(&m_cs
);
57 CReaderWriterLockNonReentrance::~CReaderWriterLockNonReentrance()
59 _ASSERT( (NULL
== m_hSafeToReadEvent
) &&
60 (NULL
== m_hSafeToWriteEvent
) );
61 DeleteCriticalSection(&m_cs
);
64 bool CReaderWriterLockNonReentrance::_ReaderWait(DWORD dwTimeout
) throw()
68 ++m_iNumOfReaderWaiting
;
69 if(NULL
== m_hSafeToReadEvent
)
71 m_hSafeToReadEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
74 if(INFINITE
== dwTimeout
) // INFINITE is a special value
76 while (0 != m_iNumOfWriter
)
79 WaitForSingleObject(m_hSafeToReadEvent
, INFINITE
);
80 // There might be one or more Writers entered, that's
81 // why we need loop here
85 ++m_iNumOfReaderEntered
;
92 DWORD
const dwBeginTime
= GetTickCount();
93 DWORD dwConsumedTime
= 0;
97 blCanRead
= (WAIT_OBJECT_0
== WaitForSingleObject(m_hSafeToReadEvent
,
98 dwTimeout
- dwConsumedTime
));
102 if(0 == m_iNumOfWriter
)
104 // Regardless timeout or not, there is no Writer
105 // So it's safe to be Reader right now
106 ++m_iNumOfReaderEntered
;
111 if(FALSE
== blCanRead
)
113 // Timeout after waiting
117 // There are some Writers have just entered
118 // So leave CS and prepare to try again
121 dwConsumedTime
= GetTickCount() - dwBeginTime
;
122 if(dwConsumedTime
> dwTimeout
)
124 // Don't worry why the code here looks stupid
125 // Because this case rarely happens, it's better
126 // to optimize code for the usual case
134 if(0==--m_iNumOfReaderWaiting
)
136 CloseHandle(m_hSafeToReadEvent
);
137 m_hSafeToReadEvent
= NULL
;
143 void CReaderWriterLockNonReentrance::_ReaderRelease()
145 INT _iNumOfReaderEntered
= --m_iNumOfReaderEntered
;
146 _ASSERT(0 <= _iNumOfReaderEntered
);
148 if( (0 == _iNumOfReaderEntered
) &&
149 (NULL
!= m_hSafeToWriteEvent
) )
151 SetEvent(m_hSafeToWriteEvent
);
155 bool CReaderWriterLockNonReentrance::_WriterWaitAndLeaveCSIfSuccess(DWORD dwTimeout
)
158 _ASSERT(0 != dwTimeout
);
160 // Increase Writer-counter & reset Reader-event if necessary
161 INT _iNumOfWriter
= ++m_iNumOfWriter
;
162 if( (1 == _iNumOfWriter
) && (NULL
!= m_hSafeToReadEvent
) )
164 ResetEvent(m_hSafeToReadEvent
);
167 if(NULL
== m_hSafeToWriteEvent
)
169 m_hSafeToWriteEvent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
173 bool blCanWrite
= (WAIT_OBJECT_0
== WaitForSingleObject(m_hSafeToWriteEvent
, dwTimeout
));
174 if(FALSE
== blCanWrite
)
176 // Undo what we changed after timeout
178 if(0 == --m_iNumOfWriter
)
180 CloseHandle(m_hSafeToWriteEvent
);
181 m_hSafeToWriteEvent
= NULL
;
183 if(0 == m_iNumOfReaderEntered
)
185 // Although it was timeout, it's still safe to be writer now
190 else if(m_hSafeToReadEvent
)
192 SetEvent(m_hSafeToReadEvent
);
199 bool CReaderWriterLockNonReentrance::_UpgradeToWriterLockAndLeaveCS(DWORD dwTimeout
) throw()
201 _ASSERT(m_iNumOfReaderEntered
> 0);
209 --m_iNumOfReaderEntered
;
210 bool blCanWrite
= _WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
211 if(FALSE
== blCanWrite
)
213 // Now analyze why it was failed to have suitable action
214 if(0 == m_iNumOfWriter
)
216 _ASSERT(0 < m_iNumOfReaderEntered
);
217 // There are some readers still owning the lock
218 // It's safe to be a reader again after failure
219 ++m_iNumOfReaderEntered
;
223 // Reach to here, it's NOT safe to be a reader immediately
224 _ReaderWait(INFINITE
);
225 if(1 == m_iNumOfReaderEntered
)
227 // After wait, now it's safe to be writer
228 _ASSERT(0 == m_iNumOfWriter
);
229 m_iNumOfReaderEntered
= 0;
240 void CReaderWriterLockNonReentrance::_WriterRelease(bool blDowngrade
)
242 _ASSERT(0 == m_iNumOfReaderEntered
);
246 ++m_iNumOfReaderEntered
;
249 if(0 == --m_iNumOfWriter
)
251 if(NULL
!= m_hSafeToWriteEvent
)
253 CloseHandle(m_hSafeToWriteEvent
);
254 m_hSafeToWriteEvent
= NULL
;
257 if(m_hSafeToReadEvent
)
259 SetEvent(m_hSafeToReadEvent
);
264 //////////////////////////////////////////////////////////////////////////
265 // Some WRITERs are queued
266 _ASSERT( (0 < m_iNumOfWriter
) && (NULL
!= m_hSafeToWriteEvent
));
268 if(FALSE
== blDowngrade
)
270 SetEvent(m_hSafeToWriteEvent
);
275 bool CReaderWriterLockNonReentrance::AcquireReaderLock(DWORD dwTimeout
)
280 if(0 == m_iNumOfWriter
)
282 // Enter successful without wait
283 ++m_iNumOfReaderEntered
;
288 blCanRead
= (dwTimeout
)? _ReaderWait(dwTimeout
) : FALSE
;
295 void CReaderWriterLockNonReentrance::ReleaseReaderLock()
302 bool CReaderWriterLockNonReentrance::AcquireWriterLock(DWORD dwTimeout
)
307 if(0 == (m_iNumOfWriter
| m_iNumOfReaderEntered
))
312 else if(0 == dwTimeout
)
318 blCanWrite
= _WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
329 void CReaderWriterLockNonReentrance::ReleaseWriterLock()
332 _WriterRelease(FALSE
);
336 void CReaderWriterLockNonReentrance::DowngradeFromWriterLock()
339 _WriterRelease(TRUE
);
343 bool CReaderWriterLockNonReentrance::UpgradeToWriterLock(DWORD dwTimeout
) throw()
346 return _UpgradeToWriterLockAndLeaveCS(dwTimeout
);
349 // END CReaderWriterLockNonReentrance implementation
350 ///////////////////////////////////////////////////////
352 ///////////////////////////////////////////////////////
353 // CReaderWriterLock implementation
355 #define READER_RECURRENCE_UNIT 0x00000001
356 #define READER_RECURRENCE_MASK 0x0000FFFF
357 #define WRITER_RECURRENCE_UNIT 0x00010000
359 CReaderWriterLock::CReaderWriterLock()
363 CReaderWriterLock::~CReaderWriterLock()
367 bool CReaderWriterLock::AcquireReaderLock(DWORD dwTimeout
)
369 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
372 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
374 if(ite
!= m_map
.end())
376 //////////////////////////////////////////////////////////////////////////
377 // Current thread was already a WRITER or READER
378 _ASSERT(0 < ite
->second
);
379 ite
->second
+= READER_RECURRENCE_UNIT
;
385 if(0 == m_impl
.m_iNumOfWriter
)
387 // There is NO WRITER on this RW object
388 // Current thread is going to be a READER
389 ++m_impl
.m_iNumOfReaderEntered
;
390 m_map
.emplace(dwCurrentThreadId
, READER_RECURRENCE_UNIT
);
402 bool blCanRead
= m_impl
._ReaderWait(dwTimeout
);
405 m_map
.emplace(dwCurrentThreadId
, READER_RECURRENCE_UNIT
);
412 void CReaderWriterLock::ReleaseReaderLock()
414 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
417 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
418 _ASSERT( (ite
!= m_map
.end()) && (READER_RECURRENCE_MASK
& ite
->second
));
420 const DWORD dwThreadState
= (ite
->second
-= READER_RECURRENCE_UNIT
);
421 if(0 == dwThreadState
)
424 m_impl
._ReaderRelease();
429 bool CReaderWriterLock::AcquireWriterLock(DWORD dwTimeout
)
431 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
435 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
437 if(ite
!= m_map
.end())
439 _ASSERT(0 < ite
->second
);
441 if(ite
->second
>= WRITER_RECURRENCE_UNIT
)
443 // Current thread was already a WRITER
444 ite
->second
+= WRITER_RECURRENCE_UNIT
;
449 // Current thread was already a READER
450 _ASSERT(1 <= m_impl
.m_iNumOfReaderEntered
);
451 if(1 == m_impl
.m_iNumOfReaderEntered
)
453 // This object is owned by ONLY current thread for READ
454 // There might be some threads queued to be WRITERs
455 // but for effectiveness (higher throughput), we allow current
456 // thread upgrading to be WRITER right now
457 m_impl
.m_iNumOfReaderEntered
= 0;
458 ++m_impl
.m_iNumOfWriter
;
459 ite
->second
+= WRITER_RECURRENCE_UNIT
;
464 // Try upgrading from reader to writer
465 blCanWrite
= m_impl
._UpgradeToWriterLockAndLeaveCS(dwTimeout
);
469 ite
= m_map
.find(dwCurrentThreadId
);
470 ite
->second
+= WRITER_RECURRENCE_UNIT
;
476 if(0 == (m_impl
.m_iNumOfWriter
| m_impl
.m_iNumOfReaderEntered
))
478 // This RW object is not owned by any thread
479 // --> it's safe to make this thread to be WRITER
480 ++m_impl
.m_iNumOfWriter
;
481 m_map
.emplace(dwCurrentThreadId
, WRITER_RECURRENCE_UNIT
);
492 blCanWrite
= m_impl
._WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
496 m_map
.emplace(dwCurrentThreadId
, WRITER_RECURRENCE_UNIT
);
504 void CReaderWriterLock::ReleaseWriterLock()
506 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
509 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
510 _ASSERT( (ite
!= m_map
.end()) && (WRITER_RECURRENCE_UNIT
<= ite
->second
));
512 const DWORD dwThreadState
= (ite
->second
-= WRITER_RECURRENCE_UNIT
);
513 if(0 == dwThreadState
)
516 m_impl
._WriterRelease(FALSE
);
518 else if (WRITER_RECURRENCE_UNIT
> dwThreadState
)
520 // Down-grading from writer to reader
521 m_impl
._WriterRelease(TRUE
);
526 void CReaderWriterLock::ReleaseAllLocks()
528 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
531 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
532 if(ite
!= m_map
.end())
534 const DWORD dwThreadState
= ite
->second
;
536 if(WRITER_RECURRENCE_UNIT
<= dwThreadState
)
538 m_impl
._WriterRelease(FALSE
);
542 _ASSERT(0 < dwThreadState
);
543 m_impl
._ReaderRelease();
549 DWORD
CReaderWriterLock::GetCurrentThreadStatus() const throw()
552 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
555 CMapThreadToState::const_iterator ite
= m_map
.find(dwCurrentThreadId
);
556 if(ite
!= m_map
.end())
558 dwThreadState
= ite
->second
;
560 _ASSERT(dwThreadState
> 0);
568 return dwThreadState
;
571 void CReaderWriterLock::GetCurrentThreadStatus(DWORD
* lpdwReaderLockCounter
,
572 DWORD
* lpdwWriterLockCounter
) const
574 const DWORD dwThreadState
= GetCurrentThreadStatus();
576 if(NULL
!= lpdwReaderLockCounter
)
578 *lpdwReaderLockCounter
= (dwThreadState
& READER_RECURRENCE_MASK
);
581 if(NULL
!= lpdwWriterLockCounter
)
583 *lpdwWriterLockCounter
= (dwThreadState
/ WRITER_RECURRENCE_UNIT
);