eathread_spinlock.h 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. /////////////////////////////////////////////////////////////////////////////
  5. // Implements an efficient proper multithread-safe spinlock.
  6. //
  7. // A spin lock is the lightest form of mutex available. The Lock operation is
  8. // simply a loop that waits to set a shared variable. SpinLocks are not
  9. // recursive (i.e. they can only be locked once by a thread) and are
  10. // intra-process only. You have to be careful using spin locks because if you
  11. // have a high priority thread that calls Lock while a lower priority thread
  12. // has the same lock, then on many systems the higher priority thread will
  13. // use up all the CPU time waiting for the lock and the lower priority thread
  14. // will not get the CPU time needed to free the lock.
  15. //
  16. // From Usenet:
  17. // A spinlock is a machine-specific "optimized" form of mutex
  18. // ("MUTual EXclusion" device). However, you should never use
  19. // a spinlock unless you know that you have multiple threads
  20. // and that you're running on a multiprocessor. Otherwise, at
  21. // best you're wasting a lot of time. A spinlock is great for
  22. // "highly parallel" algorithms like matrix decompositions,
  23. // where the application (or runtime) "knows" (or at least goes
  24. // to lengths to ensure) that the threads participating are all
  25. // running at the same time. Unless you know that, (and, if your
  26. // code doesn't create threads, you CAN'T know that), don't even
  27. // think of using a spinlock."
  28. /////////////////////////////////////////////////////////////////////////////
  29. #ifndef EATHREAD_EATHREAD_SPINLOCK_H
  30. #define EATHREAD_EATHREAD_SPINLOCK_H
  31. #include <EABase/eabase.h>
  32. #include <eathread/eathread.h>
  33. #include <new> // include new for placement new operator
  34. #if defined(EA_PROCESSOR_X86)
  35. // The reference x86 code works fine, as there is little that assembly
  36. // code can do to improve it by much, assuming that the code is compiled
  37. // in an optimized way. With VC7 on the PC platform, compiling with
  38. // optimization set to 'minimize size' and most other optimizations
  39. // enabled yielded code that was similar to Intel reference asm code.
  40. // However, when the compiler was set to minimize size and enable inlining,
  41. // it created an implementation of the Lock function that was less optimal.
  42. // #include <eathread/x86/eathread_spinlock_x86.h>
  43. #elif defined(EA_PROCESSOR_IA64)
  44. // The reference code below is probably fine.
  45. // #include <eathread/ia64/eathread_spinlock_ia64.h>
  46. #endif
  47. #if defined(EA_PRAGMA_ONCE_SUPPORTED)
  48. #pragma once // Some compilers (e.g. VC++) benefit significantly from using this. We've measured 3-4% build speed improvements in apps as a result.
  49. #endif
  50. // The above header files would define EA_THREAD_SPINLOCK_IMPLEMENTED.
  51. #if !defined(EA_THREAD_SPINLOCK_IMPLEMENTED)
  52. // We provide an implementation that works for all systems but is less optimal.
  53. #include <eathread/eathread_sync.h>
  54. #include <eathread/eathread_atomic.h>
  55. namespace EA
  56. {
  57. namespace Thread
  58. {
  59. /// class SpinLock
  60. ///
  61. /// Spinlocks are high-performance locks designed for special circumstances.
  62. /// As such, they are not 'recursive' -- you cannot lock a spinlock twice.
  63. /// Spinlocks have no explicit awareness of threading, but they are explicitly
  64. /// thread-safe.
  65. ///
  66. /// You do not want to use spin locks as a *general* replacement for mutexes or
  67. /// critical sections, even if you know your mutex use won't be recursive.
  68. /// The reason for this is due to thread scheduling and thread priority issues.
  69. /// A spinlock is not a kernel- or threading-kernel-level object and thus while
  70. /// this gives it a certain amount of speed, it also means that if you have a
  71. /// low priority thread thread with a spinlock locked and a high priority thread
  72. /// waiting for the spinlock, the program will hang, possibly indefinitely,
  73. /// because the thread scheduler is giving all its time to the high priority
  74. /// thread which happens to be stuck.
  75. ///
  76. /// On the other hand, when judiciously used, a spin lock can yield significantly
  77. /// higher performance than general mutexes, especially on platforms where mutex
  78. /// locking is particularly expensive or on multiprocessing systems.
  79. ///
  80. class SpinLock
  81. {
  82. protected: // Declared at the top because otherwise some compilers fail to compile inline functions below.
  83. AtomicInt32 mAI; /// A value of 0 means unlocked, while 1 means locked.
  84. public:
  85. SpinLock();
  86. void Lock();
  87. bool TryLock();
  88. bool IsLocked();
  89. void Unlock();
  90. void* GetPlatformData();
  91. };
  92. /// SpinLockFactory
  93. ///
  94. /// Implements a factory-based creation and destruction mechanism for class Spinlock.
  95. /// A primary use of this would be to allow the Spinlock implementation to reside in
  96. /// a private library while users of the class interact only with the interface
  97. /// header and the factory. The factory provides conventional create/destroy
  98. /// semantics which use global operator new, but also provides manual construction/
  99. /// destruction semantics so that the user can provide for memory allocation
  100. /// and deallocation.
  101. class EATHREADLIB_API SpinLockFactory
  102. {
  103. public:
  104. static SpinLock* CreateSpinLock();
  105. static void DestroySpinLock(SpinLock* pSpinLock);
  106. static size_t GetSpinLockSize();
  107. static SpinLock* ConstructSpinLock(void* pMemory);
  108. static void DestructSpinLock(SpinLock* pSpinLock);
  109. };
  110. } // namespace Thread
  111. } // namespace EA
  112. #endif // EA_THREAD_SPINLOCK_IMPLEMENTED
  113. namespace EA
  114. {
  115. namespace Thread
  116. {
  117. /// class AutoSpinLock
  118. /// An AutoSpinLock locks the SpinLock in its constructor and
  119. /// unlocks the SpinLock in its destructor (when it goes out of scope).
  120. class AutoSpinLock
  121. {
  122. public:
  123. AutoSpinLock(SpinLock& spinLock);
  124. ~AutoSpinLock();
  125. protected:
  126. SpinLock& mSpinLock;
  127. protected:
  128. // Prevent copying by default, as copying is dangerous.
  129. AutoSpinLock(const AutoSpinLock&);
  130. const AutoSpinLock& operator=(const AutoSpinLock&);
  131. };
  132. } // namespace Thread
  133. } // namespace EA
  134. ///////////////////////////////////////////////////////////////////////////////
  135. // inlines
  136. ///////////////////////////////////////////////////////////////////////////////
  137. namespace EA
  138. {
  139. namespace Thread
  140. {
  141. extern Allocator* gpAllocator;
  142. ///////////////////////////////////////////////////////////////////////
  143. // SpinLock
  144. ///////////////////////////////////////////////////////////////////////
  145. inline
  146. SpinLock::SpinLock()
  147. : mAI(0)
  148. {
  149. }
  150. inline
  151. void SpinLock::Lock()
  152. {
  153. Top: // Due to modern processor branch prediction, the compiler will optimize better for true branches and so we do a manual goto loop here.
  154. if(mAI.SetValueConditional(1, 0))
  155. return;
  156. // The loop below is present because the SetValueConditional
  157. // call above is likely to be significantly more expensive and
  158. // thus we benefit by polling before attempting the real thing.
  159. // This is a common practice and is recommended by Intel, etc.
  160. while (mAI.GetValue() != 0)
  161. {
  162. #ifdef EA_THREAD_COOPERATIVE
  163. ThreadSleep();
  164. #else
  165. EAProcessorPause();
  166. #endif
  167. }
  168. goto Top;
  169. }
  170. inline
  171. bool SpinLock::TryLock()
  172. {
  173. return mAI.SetValueConditional(1, 0);
  174. }
  175. inline
  176. bool SpinLock::IsLocked()
  177. {
  178. return mAI.GetValueRaw() != 0;
  179. }
  180. inline
  181. void SpinLock::Unlock()
  182. {
  183. EAT_ASSERT(IsLocked());
  184. mAI.SetValue(0);
  185. }
  186. inline
  187. void* SpinLock::GetPlatformData()
  188. {
  189. return &mAI;
  190. }
  191. ///////////////////////////////////////////////////////////////////////
  192. // SpinLockFactory
  193. ///////////////////////////////////////////////////////////////////////
  194. inline
  195. SpinLock* SpinLockFactory::CreateSpinLock()
  196. {
  197. if(gpAllocator)
  198. return new(gpAllocator->Alloc(sizeof(SpinLock))) SpinLock;
  199. else
  200. return new SpinLock;
  201. }
  202. inline
  203. void SpinLockFactory::DestroySpinLock(SpinLock* pSpinLock)
  204. {
  205. if(gpAllocator)
  206. {
  207. pSpinLock->~SpinLock();
  208. gpAllocator->Free(pSpinLock);
  209. }
  210. else
  211. delete pSpinLock;
  212. }
  213. inline
  214. size_t SpinLockFactory::GetSpinLockSize()
  215. {
  216. return sizeof(SpinLock);
  217. }
  218. inline
  219. SpinLock* SpinLockFactory::ConstructSpinLock(void* pMemory)
  220. {
  221. return new(pMemory) SpinLock;
  222. }
  223. EA_DISABLE_VC_WARNING(4100) // Compiler mistakenly claims pSpinLock is unreferenced
  224. inline
  225. void SpinLockFactory::DestructSpinLock(SpinLock* pSpinLock)
  226. {
  227. pSpinLock->~SpinLock();
  228. }
  229. EA_RESTORE_VC_WARNING()
  230. ///////////////////////////////////////////////////////////////////////
  231. // AutoSpinLock
  232. ///////////////////////////////////////////////////////////////////////
  233. inline
  234. AutoSpinLock::AutoSpinLock(SpinLock& spinLock)
  235. : mSpinLock(spinLock)
  236. {
  237. mSpinLock.Lock();
  238. }
  239. inline
  240. AutoSpinLock::~AutoSpinLock()
  241. {
  242. mSpinLock.Unlock();
  243. }
  244. } // namespace Thread
  245. } // namespace EA
  246. #endif // EATHREAD_EATHREAD_SPINLOCK_H