Instantly share code, notes, and snippets.

Embed
What would you like to do?
portable lock-free reader/writer lock for C++
class rw_spin_lock
{
public:
rw_spin_lock()
{
_readers = 0;
}
public:
void acquire_reader()
{
int retry = 0;
while (true)
{
uint32_t prev_readers = _readers;
if (prev_readers != HAS_WRITER)
{
uint32_t new_readers = prev_readers + 1;
if (_readers.compare_exchange_weak(prev_readers, new_readers))
{
// we've won the race
return;
}
}
retry++;
if (retry > RETRY_THRESHOLD)
{
retry = 0;
this_thread::yield();
}
}
}
void release_reader()
{
int retry = 0;
while (true)
{
uint32_t prev_readers = _readers;
if (prev_readers != HAS_WRITER && prev_readers > 0)
{
uint32_t new_readers = prev_readers - 1;
if (_readers.compare_exchange_weak(prev_readers, new_readers))
{
// we've won the race
return;
}
}
retry++;
if (retry > RETRY_THRESHOLD)
{
retry = 0;
this_thread::yield();
}
}
}
void acquire_writer()
{
int retry = 0;
while (true)
{
uint32_t prev_readers = _readers;
if (prev_readers == 0)
{
if (_readers.compare_exchange_weak(prev_readers, HAS_WRITER))
{
// we've won the race
return;
}
}
retry++;
if (retry > RETRY_THRESHOLD)
{
// save some cpu cycles
retry = 0;
this_thread::yield();
}
}
}
void release_writer()
{
int retry = 0;
while (true)
{
uint32_t prev_readers = _readers;
if (prev_readers == HAS_WRITER)
{
if (_readers.compare_exchange_weak(prev_readers, 0))
{
// we've won the race
return;
}
}
retry++;
if (retry > RETRY_THRESHOLD)
{
// save some cpu cycles
retry = 0;
this_thread::yield();
}
}
}
private:
const uint32_t HAS_WRITER = 0xffffffff;
const int RETRY_THRESHOLD = 100;
std::atomic<uint32_t> _readers;
};
class reader_lock
{
public:
reader_lock(rw_spin_lock &lock) : _lock(lock)
{
_lock.acquire_reader();
}
~reader_lock()
{
_lock.release_reader();
}
private:
rw_spin_lock &_lock;
};
class writer_lock
{
public:
writer_lock(rw_spin_lock &lock) : _lock(lock)
{
_lock.acquire_writer();
}
~writer_lock()
{
_lock.release_writer();
}
private:
rw_spin_lock &_lock;
};
@X-Ryl669

This comment has been minimized.

Show comment
Hide comment
@X-Ryl669

X-Ryl669 Apr 9, 2018

This scheme is prone to writer starvation (typically, if you have multiple thread acquiring the reader and the another one trying to acquire the writer, it's very likely that the writer will never get any chance to perform so - there's always at least one reader holding the lock). You should add a bool writer_waiting member that's set to true when entering acquireWriter (and to false before releasing the counter to 0) and that's checked in the acquire_reader before CAS'ing on the counter.

X-Ryl669 commented Apr 9, 2018

This scheme is prone to writer starvation (typically, if you have multiple thread acquiring the reader and the another one trying to acquire the writer, it's very likely that the writer will never get any chance to perform so - there's always at least one reader holding the lock). You should add a bool writer_waiting member that's set to true when entering acquireWriter (and to false before releasing the counter to 0) and that's checked in the acquire_reader before CAS'ing on the counter.

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