cpp-ipc/include/libipc/spin_lock.h
2022-02-27 17:13:28 +08:00

180 lines
4.8 KiB
C++

/**
* @file spin_lock.h
* @author mutouyun (orz@orzz.org)
* @brief Define spin locks
* @date 2022-02-27
*/
#pragma once
#include <atomic>
#include <thread>
#include <chrono>
#include <limits>
#include <type_traits>
#include <utility>
#include "libipc/detect_plat.h"
#include "libipc/def.h"
////////////////////////////////////////////////////////////////
/// Gives hint to processor that improves performance of spin-wait loops.
////////////////////////////////////////////////////////////////
#pragma push_macro("LIBIPC_LOCK_PAUSE_")
#undef LIBIPC_LOCK_PAUSE_
#if defined(LIBIPC_CC_MSVC)
# include <Windows.h> // YieldProcessor
/*
* @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms687419(v=vs.85).aspx
* Not for intel c++ compiler, so ignore http://software.intel.com/en-us/forums/topic/296168
*/
# define LIBIPC_LOCK_PAUSE_() YieldProcessor()
#elif defined(LIBIPC_CC_GNUC)
# if defined(LIBIPC_INSTR_X86_64)
/*
* @see Intel(R) 64 and IA-32 Architectures Software Developer's Manual V2
* PAUSE-Spin Loop Hint, 4-57
* http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html?wapkw=instruction+set+reference
*/
# define LIBIPC_LOCK_PAUSE_() __asm__ __volatile__("pause")
# elif defined(LIBIPC_INSTR_I64)
/*
* @see Intel(R) Itanium(R) Architecture Developer's Manual, Vol.3
* hint - Performance Hint, 3:145
* http://www.intel.com/content/www/us/en/processors/itanium/itanium-architecture-vol-3-manual.html
*/
# define LIBIPC_LOCK_PAUSE_() __asm__ __volatile__ ("hint @pause")
#elif defined(LIBIPC_INSTR_ARM)
/*
* @see ARM Architecture Reference Manuals (YIELD)
* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.architecture.reference/index.html
*/
# define LIBIPC_LOCK_PAUSE_() __asm__ __volatile__ ("yield")
# endif
#endif /*compilers*/
#if !defined(LIBIPC_LOCK_PAUSE_)
/*
* Just use a compiler fence, prevent compiler from optimizing loop
*/
# define LIBIPC_LOCK_PAUSE_() std::atomic_signal_fence(std::memory_order_seq_cst)
#endif /*!defined(LIBIPC_LOCK_PAUSE_)*/
////////////////////////////////////////////////////////////////
/// Yield to other threads
////////////////////////////////////////////////////////////////
LIBIPC_NAMESPACE_BEG_
template <typename K>
inline void yield(K &k) noexcept {
if (k < 4) { /* Do nothing */ }
else
if (k < 16) { LIBIPC_LOCK_PAUSE_(); }
else
if (k < 32) { std::this_thread::yield(); }
else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return;
}
++k;
}
template <std::size_t N = 32, typename K, typename F>
inline void sleep(K &k, F &&f) {
if (k < static_cast<K>(N)) {
std::this_thread::yield();
}
else {
static_cast<void>(std::forward<F>(f)());
return;
}
++k;
}
template <std::size_t N = 32, typename K>
inline void sleep(K &k) {
sleep<N>(k, [] {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
});
}
} // namespace ipc
#pragma pop_macro("LIBIPC_LOCK_PAUSE_")
namespace ipc {
class spin_lock {
std::atomic<unsigned> lc_ { 0 };
public:
void lock(void) noexcept {
for (unsigned k = 0;
lc_.exchange(1, std::memory_order_acquire);
yield(k)) ;
}
void unlock(void) noexcept {
lc_.store(0, std::memory_order_release);
}
};
class rw_lock {
using lc_ui_t = unsigned;
std::atomic<lc_ui_t> lc_ { 0 };
enum : lc_ui_t {
w_mask = (std::numeric_limits<std::make_signed_t<lc_ui_t>>::max)(), // b 0111 1111
w_flag = w_mask + 1 // b 1000 0000
};
public:
rw_lock() = default;
rw_lock(const rw_lock &) = delete;
rw_lock &operator=(const rw_lock &) = delete;
rw_lock(rw_lock &&) = delete;
rw_lock &operator=(rw_lock &&) = delete;
void lock() noexcept {
for (unsigned k = 0;;) {
auto old = lc_.fetch_or(w_flag, std::memory_order_acq_rel);
if (!old) return; // got w-lock
if (!(old & w_flag)) break; // other thread having r-lock
yield(k); // other thread having w-lock
}
// wait for reading finished
for (unsigned k = 0;
lc_.load(std::memory_order_acquire) & w_mask;
yield(k)) ;
}
void unlock() noexcept {
lc_.store(0, std::memory_order_release);
}
void lock_shared() noexcept {
auto old = lc_.load(std::memory_order_acquire);
for (unsigned k = 0;;) {
// if w_flag set, just continue
if (old & w_flag) {
yield(k);
old = lc_.load(std::memory_order_acquire);
}
// otherwise try cas lc + 1 (set r-lock)
else if (lc_.compare_exchange_weak(old, old + 1, std::memory_order_release)) {
return;
}
// set r-lock failed, old has been updated
}
}
void unlock_shared() noexcept {
lc_.fetch_sub(1, std::memory_order_release);
}
};
LIBIPC_NAMESPACE_END_