Program Listing for File spinlock.hpp¶
↰ Return to documentation for file (include/ripple/utility/spinlock.hpp
)
#ifndef RIPPLE_UTILITY_SPINLOCK_HPP
#define RIPPLE_UTILITY_SPINLOCK_HPP
#include <atomic>
#include <chrono>
#include <thread>
#include <cstdint>
namespace ripple {
struct Spinlock {
private:
// clang-format off
static constexpr uint8_t free = 0;
static constexpr uint8_t locked = 1;
// clang-format
struct Sleeper {
static constexpr uint32_t max_spins = 4000;
static auto sleep() noexcept -> void {
using namespace std::chrono_literals;
// Sleep for an amount which will let the kernel schedule us for the min
// duration, which is usually somewhere between 1 and 10ms.
std::this_thread::sleep_for(200us);
}
auto wait() noexcept -> void {
if (spincount_ < max_spins) {
spincount_++;
// Essentially _mm_pause() and a memory barrier in one instruction.
// Just to make sure that there is no memory reordering which might
// be the case if the compiler decided to move things around.
// The pause prevents speculative loads from causing pipeline clears
// due to memory ordering mis-speculation.
asm volatile("pause" ::: "memory");
return;
}
sleep();
}
private:
uint32_t spincount_ = 0;
};
public:
auto try_lock() noexcept -> bool {
return __sync_bool_compare_and_swap(&lock_, free, locked);
}
auto lock() noexcept -> void {
Sleeper sleeper;
while (!__sync_bool_compare_and_swap(&lock_, free, locked)) {
do {
sleeper.wait(); // Wait until CAS might succeed.
} while (lock_);
}
}
auto unlock() noexcept -> void {
// Memory barrier so that we can write the lock to the unlocked state.
asm volatile("" ::: "memory");
lock_ = free;
}
private:
/* TODO: Compare performance vs using std::atomic<uint8_t> */
uint8_t lock_ = free;
};
} // namespace ripple
#endif