Last active
December 8, 2017 18:25
-
-
Save jdtw/e6742cec105593f3a5ac480f3226598d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
class ReaderWriterLock | |
{ | |
public: | |
ReaderWriterLock() : m_owner(0), m_reads(0), m_writes(0) {} | |
~ReaderWriterLock() {} | |
ReaderWriterLock(const ReaderWriterLock&) = delete; | |
ReaderWriterLock& operator=(const ReaderWriterLock&) = delete; | |
ReaderWriterLock(ReaderWriterLock&&) = delete; | |
ReaderWriterLock& operator=(ReaderWriterLock&&) = delete; | |
class ScopedRelease | |
{ | |
public: | |
ScopedRelease() : ScopedRelease(nullptr) {} | |
ScopedRelease(volatile DWORD* toRelease) : m_toRelease(toRelease) {} | |
~ScopedRelease() | |
{ | |
if (m_toRelease) | |
{ | |
InterlockedDecrement(m_toRelease); | |
} | |
} | |
// No copy | |
ScopedRelease(const ScopedRelease&) = delete; | |
ScopedRelease& operator=(const ScopedRelease&) = delete; | |
ScopedRelease(ScopedRelease&& other) : m_toRelease(other.m_toRelease) | |
{ | |
// Make sure the moved-from object doesn't decrement the counter | |
other.m_toRelease = nullptr; | |
} | |
ScopedRelease& operator=(ScopedRelease&& other) | |
{ | |
if (&other != this) | |
{ | |
m_toRelease = other.m_toRelease; | |
other.m_toRelease = nullptr; | |
} | |
return *this; | |
} | |
private: | |
volatile DWORD* m_toRelease; | |
}; | |
ScopedRelease AcquireShared() | |
{ | |
return Acquire(true /* shared */); | |
} | |
ScopedRelease AcquireExclusive() | |
{ | |
return Acquire(false /* shared */); | |
} | |
private: | |
ScopedRelease Acquire(bool shared) | |
{ | |
DWORD threadId = GetCurrentThreadId(); | |
do | |
{ | |
InterlockedCompareExchange(&m_owner, threadId, 0); | |
} while (InterlockedCompareExchange(&m_owner, m_owner, m_owner) != threadId); | |
// m_owner == threadId. No other thread can increment m_reader or m_writer now. | |
// Other threads may decrement m_reader or m_writer. | |
// Spin, waiting for any exclusive locks to be released. Since we have the owner lock, | |
// no other threads can increment m_writes. | |
while (InterlockedCompareExchange(&m_writes, m_writes, m_writes) != 0); | |
if (shared) | |
{ | |
InterlockedIncrement(&m_reads); | |
} | |
else // exclusive | |
{ | |
// We already know that there are no exclusive locks. Now wait for all shared | |
// locks to be released so that we can take the exclusive lock. Since we have the | |
// owner lock, no other threads can increment m_reads. | |
while (InterlockedCompareExchange(&m_reads, m_reads, m_reads) != 0); | |
DWORD writes = InterlockedIncrement(&m_writes); | |
if (writes != 1) | |
{ | |
// Sanity check | |
__fastfail(static_cast<unsigned int>(E_UNEXPECTED)); | |
} | |
} | |
// We've incremented m_reads or m_writes at this point (thus taking a reader or writer lock, | |
// respectively). Release the owner lock. | |
DWORD prevOwner = InterlockedCompareExchange(&m_owner, 0, threadId); | |
if (prevOwner != threadId) | |
{ | |
// Sanity check. | |
__fastfail(static_cast<unsigned int>(E_UNEXPECTED)); | |
} | |
return ScopedRelease(shared ? &m_reads : &m_writes); | |
} | |
private: | |
// m_owner is used in a spinlock, and contains the "owning" thread ID. | |
// Only the owning thread may increment m_reads or m_writes. Other | |
// threads may decrement those values, though. | |
volatile DWORD m_owner; | |
volatile DWORD m_reads; | |
volatile DWORD m_writes; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And I'm stupidly not sleeping in my spin-loops. Duh.