Skip to content

Instantly share code, notes, and snippets.

@jdtw
Last active December 8, 2017 18:25
Show Gist options
  • Save jdtw/e6742cec105593f3a5ac480f3226598d to your computer and use it in GitHub Desktop.
Save jdtw/e6742cec105593f3a5ac480f3226598d to your computer and use it in GitHub Desktop.
#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;
};
@jdtw
Copy link
Author

jdtw commented Dec 8, 2017

And I'm stupidly not sleeping in my spin-loops. Duh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment