Here is my implementation of std compatible RWSpinLock using 2 atomics.
I think in cases when spinlock is free, it should take just 1 atomic write operation.
class RWSpinLock{
std::atomic<int> readers_count{0};
std::atomic<bool> write_now{false};
public:
void lock() {
while (!write_now.exchange(true, std::memory_order_acquire)){
std::this_thread::yield();
}
// wait for readers to exit
while (readers_count != 0 ){
std::this_thread::yield();
}
}
void unlock() {
write_now.store(false, std::memory_order_release);
}
void lock_shared() {
// unique_lock have priority
while(true) {
while (write_now) { // wait for unlock
std::this_thread::yield();
}
readers_count.fetch_add(1, std::memory_order_acquire);
if (write_now){
// locked while transaction? Fallback. Go another round
readers_count.fetch_sub(1, std::memory_order_release);
} else {
// all ok
return;
}
}
}
void unlock_shared() {
readers_count.fetch_sub(1, std::memory_order_release);
}
};
On unique_lock
wait for write_now flag, then wait for readers_count.
On shared_lock
wait for write_now flag, then increase readers_count. If during incrementation write_now flag becomes true, decrease readers_count and wait again for write_now (this should be relativley rare case).
P.S. I'm not sure though, about memory_order, and overall correctness (but seems fine to me).
std::this_thread::yield();
A spin lock goes into a loop trying to acquire the lock (not yielding the thread just doing a busy wait). \$\endgroup\$PAUSE
inside the inner loop. Even if it didsleep_for(1s)
in the inner loop, I'd call it morally a spinlock, not a mutex. (The defining characteristic of a proper mutex, for me, is that it wakes up at the proper time and wastes a comparatively little amount of time polling in high-contention situations.) Maybe we can compromise and call this a "polling" mutex? :) \$\endgroup\$