MicroSpinLock.h 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /*
  2. * Copyright 2015-present Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * N.B. You most likely do _not_ want to use MicroSpinLock or any
  18. * other kind of spinlock. Consider MicroLock instead.
  19. *
  20. * In short, spinlocks in preemptive multi-tasking operating systems
  21. * have serious problems and fast mutexes like std::mutex are almost
  22. * certainly the better choice, because letting the OS scheduler put a
  23. * thread to sleep is better for system responsiveness and throughput
  24. * than wasting a timeslice repeatedly querying a lock held by a
  25. * thread that's blocked, and you can't prevent userspace
  26. * programs blocking.
  27. *
  28. * Spinlocks in an operating system kernel make much more sense than
  29. * they do in userspace.
  30. */
  31. #pragma once
  32. /*
  33. * @author Keith Adams <kma@fb.com>
  34. * @author Jordan DeLong <delong.j@fb.com>
  35. */
  36. #include <array>
  37. #include <atomic>
  38. #include <cassert>
  39. #include <cstdint>
  40. #include <mutex>
  41. #include <type_traits>
  42. #include <folly/Portability.h>
  43. #include <folly/lang/Align.h>
  44. #include <folly/synchronization/detail/Sleeper.h>
  45. namespace folly {
  46. /*
  47. * A really, *really* small spinlock for fine-grained locking of lots
  48. * of teeny-tiny data.
  49. *
  50. * Zero initializing these is guaranteed to be as good as calling
  51. * init(), since the free state is guaranteed to be all-bits zero.
  52. *
  53. * This class should be kept a POD, so we can used it in other packed
  54. * structs (gcc does not allow __attribute__((__packed__)) on structs that
  55. * contain non-POD data). This means avoid adding a constructor, or
  56. * making some members private, etc.
  57. */
  58. struct MicroSpinLock {
  59. enum { FREE = 0, LOCKED = 1 };
  60. // lock_ can't be std::atomic<> to preserve POD-ness.
  61. uint8_t lock_;
  62. // Initialize this MSL. It is unnecessary to call this if you
  63. // zero-initialize the MicroSpinLock.
  64. void init() {
  65. payload()->store(FREE);
  66. }
  67. bool try_lock() {
  68. return cas(FREE, LOCKED);
  69. }
  70. void lock() {
  71. detail::Sleeper sleeper;
  72. while (!try_lock()) {
  73. do {
  74. sleeper.wait();
  75. } while (payload()->load(std::memory_order_relaxed) == LOCKED);
  76. }
  77. assert(payload()->load() == LOCKED);
  78. }
  79. void unlock() {
  80. assert(payload()->load() == LOCKED);
  81. payload()->store(FREE, std::memory_order_release);
  82. }
  83. private:
  84. std::atomic<uint8_t>* payload() {
  85. return reinterpret_cast<std::atomic<uint8_t>*>(&this->lock_);
  86. }
  87. bool cas(uint8_t compare, uint8_t newVal) {
  88. return std::atomic_compare_exchange_strong_explicit(
  89. payload(),
  90. &compare,
  91. newVal,
  92. std::memory_order_acquire,
  93. std::memory_order_relaxed);
  94. }
  95. };
  96. static_assert(
  97. std::is_pod<MicroSpinLock>::value,
  98. "MicroSpinLock must be kept a POD type.");
  99. //////////////////////////////////////////////////////////////////////
  100. /**
  101. * Array of spinlocks where each one is padded to prevent false sharing.
  102. * Useful for shard-based locking implementations in environments where
  103. * contention is unlikely.
  104. */
  105. // TODO: generate it from configure (`getconf LEVEL1_DCACHE_LINESIZE`)
  106. #define FOLLY_CACHE_LINE_SIZE 64
  107. template <class T, size_t N>
  108. struct alignas(max_align_v) SpinLockArray {
  109. T& operator[](size_t i) {
  110. return data_[i].lock;
  111. }
  112. const T& operator[](size_t i) const {
  113. return data_[i].lock;
  114. }
  115. constexpr size_t size() const {
  116. return N;
  117. }
  118. private:
  119. struct PaddedSpinLock {
  120. PaddedSpinLock() : lock() {}
  121. T lock;
  122. char padding[FOLLY_CACHE_LINE_SIZE - sizeof(T)];
  123. };
  124. static_assert(
  125. sizeof(PaddedSpinLock) == FOLLY_CACHE_LINE_SIZE,
  126. "Invalid size of PaddedSpinLock");
  127. // Check if T can theoretically cross a cache line.
  128. static_assert(
  129. max_align_v > 0 && FOLLY_CACHE_LINE_SIZE % max_align_v == 0 &&
  130. sizeof(T) <= max_align_v,
  131. "T can cross cache line boundaries");
  132. char padding_[FOLLY_CACHE_LINE_SIZE];
  133. std::array<PaddedSpinLock, N> data_;
  134. };
  135. //////////////////////////////////////////////////////////////////////
  136. typedef std::lock_guard<MicroSpinLock> MSLGuard;
  137. //////////////////////////////////////////////////////////////////////
  138. } // namespace folly