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() noexcept
44 SecureZeroMemory(this, sizeof(*this));
45 #if (_WIN32_WINNT >= 0x0403)
46 InitializeCriticalSectionAndSpinCount(&m_cs
, READER_WRITER_SPIN_COUNT
);
48 InitializeCriticalSection(&m_cs
);
52 CReaderWriterLockNonReentrance::~CReaderWriterLockNonReentrance()
54 _ASSERT(!m_hSafeToReadEvent
&& !m_hSafeToWriteEvent
);
55 DeleteCriticalSection(&m_cs
);
58 bool CReaderWriterLockNonReentrance::_ReaderWait(ULONGLONG dwTimeout
) noexcept
62 ++m_iNumOfReaderWaiting
;
63 if (!m_hSafeToReadEvent
)
64 m_hSafeToReadEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
66 if(INFINITE
== dwTimeout
) // INFINITE is a special value
68 while (0 != m_iNumOfWriter
)
71 WaitForSingleObject(m_hSafeToReadEvent
, INFINITE
);
72 // There might be one or more Writers entered, that's
73 // why we need loop here
77 ++m_iNumOfReaderEntered
;
84 ULONGLONG
const dwBeginTime
= GetTickCount64();
85 ULONGLONG dwConsumedTime
= 0;
89 blCanRead
= (WAIT_OBJECT_0
== WaitForSingleObject(m_hSafeToReadEvent
, static_cast<DWORD
>(dwTimeout
- dwConsumedTime
)));
93 if(0 == m_iNumOfWriter
)
95 // Regardless timeout or not, there is no Writer
96 // So it's safe to be Reader right now
97 ++m_iNumOfReaderEntered
;
104 // Timeout after waiting
108 // There are some Writers have just entered
109 // So leave CS and prepare to try again
112 dwConsumedTime
= GetTickCount64() - dwBeginTime
;
113 if(dwConsumedTime
> dwTimeout
)
115 // Don't worry why the code here looks stupid
116 // Because this case rarely happens, it's better
117 // to optimize code for the usual case
125 if(0==--m_iNumOfReaderWaiting
)
127 CloseHandle(m_hSafeToReadEvent
);
128 m_hSafeToReadEvent
= nullptr;
134 void CReaderWriterLockNonReentrance::_ReaderRelease() noexcept
136 INT _iNumOfReaderEntered
= --m_iNumOfReaderEntered
;
137 _ASSERT(0 <= _iNumOfReaderEntered
);
139 if (0 == _iNumOfReaderEntered
&& m_hSafeToWriteEvent
)
141 SetEvent(m_hSafeToWriteEvent
);
145 bool CReaderWriterLockNonReentrance::_WriterWaitAndLeaveCSIfSuccess(ULONGLONG dwTimeout
) noexcept
148 _ASSERT(0 != dwTimeout
);
150 // Increase Writer-counter & reset Reader-event if necessary
151 INT _iNumOfWriter
= ++m_iNumOfWriter
;
152 if (_iNumOfWriter
== 1 && m_hSafeToReadEvent
)
154 ResetEvent(m_hSafeToReadEvent
);
157 if (!m_hSafeToWriteEvent
)
159 m_hSafeToWriteEvent
= CreateEvent(nullptr, FALSE
, FALSE
, nullptr);
163 bool blCanWrite
= (WAIT_OBJECT_0
== WaitForSingleObject(m_hSafeToWriteEvent
, static_cast<DWORD
>(dwTimeout
)));
166 // Undo what we changed after timeout
168 if(0 == --m_iNumOfWriter
)
170 CloseHandle(m_hSafeToWriteEvent
);
171 m_hSafeToWriteEvent
= nullptr;
173 if(0 == m_iNumOfReaderEntered
)
175 // Although it was timeout, it's still safe to be writer now
180 else if(m_hSafeToReadEvent
)
182 SetEvent(m_hSafeToReadEvent
);
189 bool CReaderWriterLockNonReentrance::_UpgradeToWriterLockAndLeaveCS(ULONGLONG dwTimeout
) noexcept
191 _ASSERT(m_iNumOfReaderEntered
> 0);
199 --m_iNumOfReaderEntered
;
200 bool blCanWrite
= _WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
203 // Now analyze why it was failed to have suitable action
204 if(0 == m_iNumOfWriter
)
206 _ASSERT(0 < m_iNumOfReaderEntered
);
207 // There are some readers still owning the lock
208 // It's safe to be a reader again after failure
209 ++m_iNumOfReaderEntered
;
213 // Reach to here, it's NOT safe to be a reader immediately
214 _ReaderWait(INFINITE
);
215 if(1 == m_iNumOfReaderEntered
)
217 // After wait, now it's safe to be writer
218 _ASSERT(0 == m_iNumOfWriter
);
219 m_iNumOfReaderEntered
= 0;
230 void CReaderWriterLockNonReentrance::_WriterRelease(bool blDowngrade
) noexcept
232 _ASSERT(0 == m_iNumOfReaderEntered
);
236 ++m_iNumOfReaderEntered
;
239 if(0 == --m_iNumOfWriter
)
241 if (m_hSafeToWriteEvent
)
243 CloseHandle(m_hSafeToWriteEvent
);
244 m_hSafeToWriteEvent
= nullptr;
247 if(m_hSafeToReadEvent
)
249 SetEvent(m_hSafeToReadEvent
);
254 //////////////////////////////////////////////////////////////////////////
255 // Some WRITERs are queued
256 _ASSERT(m_iNumOfWriter
> 0 && m_hSafeToWriteEvent
);
260 SetEvent(m_hSafeToWriteEvent
);
265 bool CReaderWriterLockNonReentrance::AcquireReaderLock(DWORD dwTimeout
) noexcept
270 if(0 == m_iNumOfWriter
)
272 // Enter successful without wait
273 ++m_iNumOfReaderEntered
;
278 blCanRead
= (dwTimeout
)? _ReaderWait(dwTimeout
) : FALSE
;
285 void CReaderWriterLockNonReentrance::ReleaseReaderLock() noexcept
292 bool CReaderWriterLockNonReentrance::AcquireWriterLock(DWORD dwTimeout
) noexcept
297 if(0 == (m_iNumOfWriter
| m_iNumOfReaderEntered
))
302 else if(0 == dwTimeout
)
308 blCanWrite
= _WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
319 void CReaderWriterLockNonReentrance::ReleaseWriterLock() noexcept
322 _WriterRelease(FALSE
);
326 void CReaderWriterLockNonReentrance::DowngradeFromWriterLock() noexcept
329 _WriterRelease(TRUE
);
333 bool CReaderWriterLockNonReentrance::UpgradeToWriterLock(DWORD dwTimeout
) noexcept
336 return _UpgradeToWriterLockAndLeaveCS(dwTimeout
);
339 // END CReaderWriterLockNonReentrance implementation
340 ///////////////////////////////////////////////////////
342 ///////////////////////////////////////////////////////
343 // CReaderWriterLock implementation
345 #define READER_RECURRENCE_UNIT 0x00000001
346 #define READER_RECURRENCE_MASK 0x0000FFFF
347 #define WRITER_RECURRENCE_UNIT 0x00010000
349 CReaderWriterLock::CReaderWriterLock()
353 CReaderWriterLock::~CReaderWriterLock()
357 bool CReaderWriterLock::AcquireReaderLock(DWORD dwTimeout
) noexcept
359 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
362 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
364 if(ite
!= m_map
.end())
366 //////////////////////////////////////////////////////////////////////////
367 // Current thread was already a WRITER or READER
368 _ASSERT(0 < ite
->second
);
369 ite
->second
+= READER_RECURRENCE_UNIT
;
375 if(0 == m_impl
.m_iNumOfWriter
)
377 // There is NO WRITER on this RW object
378 // Current thread is going to be a READER
379 ++m_impl
.m_iNumOfReaderEntered
;
380 m_map
.emplace(dwCurrentThreadId
, READER_RECURRENCE_UNIT
);
392 bool blCanRead
= m_impl
._ReaderWait(dwTimeout
);
395 m_map
.emplace(dwCurrentThreadId
, READER_RECURRENCE_UNIT
);
402 void CReaderWriterLock::ReleaseReaderLock() noexcept
404 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
407 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
408 _ASSERT( (ite
!= m_map
.end()) && (READER_RECURRENCE_MASK
& ite
->second
));
410 const DWORD dwThreadState
= (ite
->second
-= READER_RECURRENCE_UNIT
);
411 if(0 == dwThreadState
)
414 m_impl
._ReaderRelease();
419 bool CReaderWriterLock::AcquireWriterLock(DWORD dwTimeout
) noexcept
421 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
425 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
427 if(ite
!= m_map
.end())
429 _ASSERT(0 < ite
->second
);
431 if(ite
->second
>= WRITER_RECURRENCE_UNIT
)
433 // Current thread was already a WRITER
434 ite
->second
+= WRITER_RECURRENCE_UNIT
;
439 // Current thread was already a READER
440 _ASSERT(1 <= m_impl
.m_iNumOfReaderEntered
);
441 if(1 == m_impl
.m_iNumOfReaderEntered
)
443 // This object is owned by ONLY current thread for READ
444 // There might be some threads queued to be WRITERs
445 // but for effectiveness (higher throughput), we allow current
446 // thread upgrading to be WRITER right now
447 m_impl
.m_iNumOfReaderEntered
= 0;
448 ++m_impl
.m_iNumOfWriter
;
449 ite
->second
+= WRITER_RECURRENCE_UNIT
;
454 // Try upgrading from reader to writer
455 blCanWrite
= m_impl
._UpgradeToWriterLockAndLeaveCS(dwTimeout
);
459 ite
= m_map
.find(dwCurrentThreadId
);
460 ite
->second
+= WRITER_RECURRENCE_UNIT
;
466 if(0 == (m_impl
.m_iNumOfWriter
| m_impl
.m_iNumOfReaderEntered
))
468 // This RW object is not owned by any thread
469 // --> it's safe to make this thread to be WRITER
470 ++m_impl
.m_iNumOfWriter
;
471 m_map
.emplace(dwCurrentThreadId
, WRITER_RECURRENCE_UNIT
);
482 blCanWrite
= m_impl
._WriterWaitAndLeaveCSIfSuccess(dwTimeout
);
486 m_map
.emplace(dwCurrentThreadId
, WRITER_RECURRENCE_UNIT
);
494 void CReaderWriterLock::ReleaseWriterLock() noexcept
496 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
499 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
500 _ASSERT( (ite
!= m_map
.end()) && (WRITER_RECURRENCE_UNIT
<= ite
->second
));
502 const DWORD dwThreadState
= (ite
->second
-= WRITER_RECURRENCE_UNIT
);
503 if(0 == dwThreadState
)
506 m_impl
._WriterRelease(FALSE
);
508 else if (WRITER_RECURRENCE_UNIT
> dwThreadState
)
510 // Down-grading from writer to reader
511 m_impl
._WriterRelease(TRUE
);
516 void CReaderWriterLock::ReleaseAllLocks() noexcept
518 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
521 CMapThreadToState::iterator ite
= m_map
.find(dwCurrentThreadId
);
522 if(ite
!= m_map
.end())
524 const DWORD dwThreadState
= ite
->second
;
526 if(WRITER_RECURRENCE_UNIT
<= dwThreadState
)
528 m_impl
._WriterRelease(FALSE
);
532 _ASSERT(0 < dwThreadState
);
533 m_impl
._ReaderRelease();
539 DWORD
CReaderWriterLock::GetCurrentThreadStatus() const noexcept
542 const DWORD dwCurrentThreadId
= GetCurrentThreadId();
545 CMapThreadToState::const_iterator ite
= m_map
.find(dwCurrentThreadId
);
546 if(ite
!= m_map
.end())
548 dwThreadState
= ite
->second
;
550 _ASSERT(dwThreadState
> 0);
558 return dwThreadState
;
561 void CReaderWriterLock::GetCurrentThreadStatus(DWORD
* lpdwReaderLockCounter
,
562 DWORD
* lpdwWriterLockCounter
) const noexcept
564 const DWORD dwThreadState
= GetCurrentThreadStatus();
566 if (lpdwReaderLockCounter
)
567 *lpdwReaderLockCounter
= (dwThreadState
& READER_RECURRENCE_MASK
);
569 if (lpdwWriterLockCounter
)
570 *lpdwWriterLockCounter
= (dwThreadState
/ WRITER_RECURRENCE_UNIT
);