rw_spinlock.hpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #ifndef BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
  2. #define BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED
  3. // Copyright 2023 Peter Dimov
  4. // Distributed under the Boost Software License, Version 1.0.
  5. // https://www.boost.org/LICENSE_1_0.txt
  6. #include <boost/core/yield_primitives.hpp>
  7. #include <atomic>
  8. #include <cstdint>
  9. namespace boost{
  10. namespace unordered{
  11. namespace detail{
  12. namespace foa{
  13. class rw_spinlock
  14. {
  15. private:
  16. // bit 31: locked exclusive
  17. // bit 30: writer pending
  18. // bit 29..0: reader lock count
  19. static constexpr std::uint32_t locked_exclusive_mask = 1u << 31; // 0x8000'0000
  20. static constexpr std::uint32_t writer_pending_mask = 1u << 30; // 0x4000'0000
  21. static constexpr std::uint32_t reader_lock_count_mask = writer_pending_mask - 1; // 0x3FFF'FFFF
  22. std::atomic<std::uint32_t> state_ = {};
  23. private:
  24. // Effects: Provides a hint to the implementation that the current thread
  25. // has been unable to make progress for k+1 iterations.
  26. static void yield( unsigned k ) noexcept
  27. {
  28. unsigned const sleep_every = 1024; // see below
  29. k %= sleep_every;
  30. if( k < 5 )
  31. {
  32. // Intel recommendation from the Optimization Reference Manual
  33. // Exponentially increase number of PAUSE instructions each
  34. // iteration until reaching a maximum which is approximately
  35. // one timeslice long (2^4 == 16 in our case)
  36. unsigned const pause_count = 1u << k;
  37. for( unsigned i = 0; i < pause_count; ++i )
  38. {
  39. boost::core::sp_thread_pause();
  40. }
  41. }
  42. else if( k < sleep_every - 1 )
  43. {
  44. // Once the maximum number of PAUSE instructions is reached,
  45. // we switch to yielding the timeslice immediately
  46. boost::core::sp_thread_yield();
  47. }
  48. else
  49. {
  50. // After `sleep_every` iterations of no progress, we sleep,
  51. // to avoid a deadlock if a lower priority thread has the lock
  52. boost::core::sp_thread_sleep();
  53. }
  54. }
  55. public:
  56. bool try_lock_shared() noexcept
  57. {
  58. std::uint32_t st = state_.load( std::memory_order_relaxed );
  59. if( st >= reader_lock_count_mask )
  60. {
  61. // either bit 31 set, bit 30 set, or reader count is max
  62. return false;
  63. }
  64. std::uint32_t newst = st + 1;
  65. return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
  66. }
  67. void lock_shared() noexcept
  68. {
  69. for( unsigned k = 0; ; ++k )
  70. {
  71. std::uint32_t st = state_.load( std::memory_order_relaxed );
  72. if( st < reader_lock_count_mask )
  73. {
  74. std::uint32_t newst = st + 1;
  75. if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
  76. }
  77. yield( k );
  78. }
  79. }
  80. void unlock_shared() noexcept
  81. {
  82. // pre: locked shared, not locked exclusive
  83. state_.fetch_sub( 1, std::memory_order_release );
  84. // if the writer pending bit is set, there's a writer waiting
  85. // let it acquire the lock; it will clear the bit on unlock
  86. }
  87. bool try_lock() noexcept
  88. {
  89. std::uint32_t st = state_.load( std::memory_order_relaxed );
  90. if( st & locked_exclusive_mask )
  91. {
  92. // locked exclusive
  93. return false;
  94. }
  95. if( st & reader_lock_count_mask )
  96. {
  97. // locked shared
  98. return false;
  99. }
  100. std::uint32_t newst = locked_exclusive_mask;
  101. return state_.compare_exchange_strong( st, newst, std::memory_order_acquire, std::memory_order_relaxed );
  102. }
  103. void lock() noexcept
  104. {
  105. for( unsigned k = 0; ; ++k )
  106. {
  107. std::uint32_t st = state_.load( std::memory_order_relaxed );
  108. if( st & locked_exclusive_mask )
  109. {
  110. // locked exclusive, spin
  111. }
  112. else if( ( st & reader_lock_count_mask ) == 0 )
  113. {
  114. // not locked exclusive, not locked shared, try to lock
  115. std::uint32_t newst = locked_exclusive_mask;
  116. if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
  117. }
  118. else if( st & writer_pending_mask )
  119. {
  120. // writer pending bit already set, nothing to do
  121. }
  122. else
  123. {
  124. // locked shared, set writer pending bit
  125. std::uint32_t newst = st | writer_pending_mask;
  126. state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed );
  127. }
  128. yield( k );
  129. }
  130. }
  131. void unlock() noexcept
  132. {
  133. // pre: locked exclusive, not locked shared
  134. state_.store( 0, std::memory_order_release );
  135. }
  136. };
  137. } /* namespace foa */
  138. } /* namespace detail */
  139. } /* namespace unordered */
  140. } /* namespace boost */
  141. #endif // BOOST_UNORDERED_DETAIL_FOA_RW_SPINLOCK_HPP_INCLUDED