1 //------------------------------------------------------------------------------
2 // <copyright file="ReadWriteSpinLock.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 namespace System.Web.Util {
10 using System.Threading;
11 using System.Collections;
12 using System.Globalization;
13 using Microsoft.Win32;
15 struct ReadWriteSpinLock {
20 // _bits is layed out as follows:
22 // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
23 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
24 // +-+-+---------------------------+--------------------------------+
25 // |S|W| WriteLockCount | ReadLockCount |
26 // +-+-+---------------------------+--------------------------------+
29 // S - sign bit (always zero) - By having a sign bit, operations
30 // on the ReadLockCount can use InterlockedIncrement/Decrement
32 // W - writer waiting bit - set by threads attempting write lock, preventing
33 // any further threads from acquiring read locks. This attempts to hint
34 // that updates have priority, but doesn't guarantee priority.
36 // WriteLockCount - Write lock recursion count
38 // ReadLockCount - Read lock recursion count
47 static bool s_disableBusyWaiting = (SystemInfo.GetNumProcessCPUs() == 1);
53 const int BACK_OFF_FACTORS_LENGTH = 13;
54 static readonly double [] s_backOffFactors = new double [BACK_OFF_FACTORS_LENGTH] {
55 1.020, 0.965, 0.890, 1.065,
56 1.025, 1.115, 0.940, 0.995,
57 1.050, 1.080, 0.915, 0.980,
61 const int WRITER_WAITING_MASK = (int) 0x40000000;
62 const int WRITE_COUNT_MASK = (int) 0x3FFF0000;
63 const int READ_COUNT_MASK = (int) 0x0000FFFF;
64 const int WRITER_WAITING_SHIFT = 30;
65 const int WRITE_COUNT_SHIFT = 16;
67 static bool WriterWaiting(int bits) {return ((bits & WRITER_WAITING_MASK) != 0);}
68 static int WriteLockCount(int bits) {return ((bits & WRITE_COUNT_MASK) >> WRITE_COUNT_SHIFT);}
69 static int ReadLockCount(int bits) {return (bits & READ_COUNT_MASK);}
70 static bool NoWriters(int bits) {return ((bits & WRITE_COUNT_MASK) == 0);}
71 static bool NoWritersOrWaitingWriters(int bits) {return ((bits & (WRITE_COUNT_MASK | WRITER_WAITING_MASK)) == 0);}
72 static bool NoLocks(int bits) {return ((bits & ~WRITER_WAITING_MASK) == 0);}
74 bool WriterWaiting() {return WriterWaiting(_bits);}
75 int WriteLockCount() {return WriteLockCount(_bits);}
76 int ReadLockCount() {return ReadLockCount(_bits);}
77 bool NoWriters() {return NoWriters(_bits);}
78 bool NoWritersOrWaitingWriters() {return NoWritersOrWaitingWriters(_bits);}
79 bool NoLocks() {return NoLocks(_bits);}
81 int CreateNewBits(bool writerWaiting, int writeCount, int readCount) {
82 int bits = ((writeCount << WRITE_COUNT_SHIFT) | readCount);
84 bits |= WRITER_WAITING_MASK;
91 internal /*public*/ void AcquireReaderLock() {
93 // This lock supports Writelock then Readlock
94 // from the same thread (possibly from different functions).
95 int threadId = Thread.CurrentThread.GetHashCode();
97 // Optimize for the common case by
98 if (_TryAcquireReaderLock(threadId))
101 _Spin(true, threadId);
102 Debug.Trace("Spinlock", "AcquireReaderLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
103 + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
107 internal /*public*/ void AcquireWriterLock() {
109 int threadId = Thread.CurrentThread.GetHashCode();
111 // Optimize for the common case by
112 if (_TryAcquireWriterLock(threadId))
115 _Spin(false, threadId);
117 Debug.Trace("Spinlock", "AcquireWriterLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
118 + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
122 internal /*public*/ void ReleaseReaderLock() {
125 Debug.Assert(id == 0 || id == Thread.CurrentThread.GetHashCode(), "id == 0 || id == Thread.CurrentThread.GetHashCode()");
128 int n = Interlocked.Decrement(ref _bits);
130 Debug.Assert(n >= 0, "n >= 0");
131 Debug.Trace("Spinlock", "ReleaseReaderLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
132 + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
136 void AlterWriteCountHoldingWriterLock(int oldBits, int delta) {
137 int readLockCount = ReadLockCount(oldBits);
138 int oldWriteLockCount = WriteLockCount(oldBits);
139 int newWriteLockCount = oldWriteLockCount + delta;
140 Debug.Assert(newWriteLockCount >= 0, "newWriteLockCount >= 0");
146 // Since we own the lock, the only change that can be
147 // made by another thread to _bits is to add the writer-waiting bit.
149 Debug.Assert(WriteLockCount(oldBits) == oldWriteLockCount, "WriteLockCount(oldBits) == oldWriteLockCount");
150 Debug.Assert(ReadLockCount(oldBits) == readLockCount, "ReadLockCount(oldBits) == readLockCount");
151 newBits = CreateNewBits(WriterWaiting(oldBits), newWriteLockCount, readLockCount);
152 test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
153 if (test == oldBits) {
161 internal /*public*/ void ReleaseWriterLock() {
164 Debug.Assert(id == Thread.CurrentThread.GetHashCode(), "id == Thread.CurrentThread.GetHashCode()");
168 int writeLockCount = WriteLockCount(oldBits);
169 Debug.Assert(writeLockCount > 0, "writeLockCount > 0");
170 if (writeLockCount == 1) {
171 // Reset the id before releasing count so that
172 // AcquireRead works correctly.
176 AlterWriteCountHoldingWriterLock(oldBits, -1);
177 Debug.Trace("Spinlock", "ReleaseWriterLock: _bits=" + _bits.ToString("x8", CultureInfo.InvariantCulture)
178 + " _id= " + _id.ToString("x8", CultureInfo.InvariantCulture));
182 bool _TryAcquireWriterLock(int threadId) {
188 if (id == threadId) {
189 // we can just pound in the correct value
190 AlterWriteCountHoldingWriterLock(oldBits, +1);
194 if (id == 0 && NoLocks(oldBits)) {
195 newBits = CreateNewBits(false, 1, 0);
196 test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
197 if (test == oldBits) {
199 Debug.Assert(id == 0);
208 // If there is contention, make sure the WRITER_WAITING bit is set.
209 // Note: this blocks readers from using a value that is about to be changed
210 if (!WriterWaiting(oldBits)) {
211 // hammer on _bits until the bit is set
213 newBits = (oldBits | WRITER_WAITING_MASK);
214 test = Interlocked.CompareExchange(ref _bits, newBits, oldBits);
225 bool _TryAcquireReaderLock(int threadId) {
230 if (!NoWriters(oldBits)) {
234 else if (id != threadId) {
238 if (Interlocked.CompareExchange(ref _bits, oldBits + 1, oldBits) == oldBits) {
248 void _Spin(bool isReaderLock, int threadId) {
250 const int LOCK_MAXIMUM_SPINS = 10000; // maximum allowable spin count
251 const int LOCK_DEFAULT_SPINS = 4000; // default spin count
252 const int LOCK_MINIMUM_SPINS = 100; // minimum allowable spin count
257 { // limit scope of temp. stack vars to calculation of baseSpin2
259 // Alternatives for threadId include a static counter
260 // or the low DWORD of QueryPerformanceCounter().
261 double randomBackoffFactor = s_backOffFactors[Math.Abs(threadId) % BACK_OFF_FACTORS_LENGTH];
262 baseSpins = (int)(LOCK_DEFAULT_SPINS * randomBackoffFactor);
263 baseSpins = Math.Min(LOCK_MAXIMUM_SPINS, baseSpins);
264 baseSpins = Math.Max(baseSpins, LOCK_MINIMUM_SPINS);
267 DateTime utcSpinStartTime = DateTime.UtcNow; // error if struct not initialized
269 // hand-optimize loop: Increase locality by copying static variables
270 // onto the stack (this will reduce cache misses after a contact
271 // switch induced by Sleep()).
272 bool disableBusyWaiting = s_disableBusyWaiting;
276 if (_TryAcquireReaderLock(threadId)) {
281 if (_TryAcquireWriterLock(threadId)) {
286 // if 1 cpu, or cpu affinity is set to 1, spinning is a waste of time
287 if (disableBusyWaiting) {
289 Thread.Sleep(sleepTime);
291 // Avoid priority inversion: 0, 1, 0, 1,...
295 int spinCount = baseSpins;
297 // Check no more than baseSpins times then yield.
298 // It is important not to use the InterlockedExchange in the
299 // inner loop in order to minimize system memory bus traffic.
302 // If the lock is available break spinning and
306 if (NoWritersOrWaitingWriters()) {
316 if (--spinCount < 0) {
318 Thread.Sleep(sleepTime);
320 // Backoff algorithm: reduce (or increase) busy wait time
322 // LOCK_MINIMUM_SPINS <= baseSpins <= LOCK_MAXIMUM_SPINS
323 //baseSpins = Math.Min(LOCK_MAXIMUM_SPINS, baseSpins); //= min(LOCK_MAXIMUM_SPINS, baseSpins)
324 baseSpins = Math.Max(baseSpins, LOCK_MINIMUM_SPINS); //= max(baseSpins, LOCK_MINIMUM_SPINS);
325 spinCount = baseSpins;
327 // Using Sleep(0) leads to the possibility of priority
328 // inversion. Sleep(0) only yields the processor if
329 // there's another thread of the same priority that's
330 // ready to run. If a high-priority thread is trying to
331 // acquire the lock, which is held by a low-priority
332 // thread, then the low-priority thread may never get
333 // scheduled and hence never free the lock. NT attempts
334 // to avoid priority inversions by temporarily boosting
335 // the priority of low-priority runnable threads, but the
336 // problem can still occur if there's a medium-priority
337 // thread that's always runnable. If Sleep(1) is used,
338 // then the thread unconditionally yields the CPU. We
339 // only do this for the second and subsequent even
340 // iterations, since a millisecond is a long time to wait
341 // if the thread can be scheduled in again sooner
342 // (~100,000 instructions).
343 // Avoid priority inversion: 0, 1, 0, 1,...
347 // kill about 20 clock cycles on this proc
357 } // ReadWriteSpinLock
359 } // namespace System.Web.Util
363 // This ReaderWriterSpinlock is a combination of the
364 // original lightweight (4 byte) System.Web.Util.ReadWriteSpinLock
365 // and the lightweight (4 byte) exclusive lock (SmallSpinLock) used
366 // in the George Reilly's LKRHash (see http://georgere/work/lkrhash).
368 // In an effort to support reentrancy during writes we are squirreling
369 // away the thread id of the thread holding the write lock into the upper
370 // 16 bits of the lock count. This is possible as long as thread ids stay
371 // smaller than 7FFF. Anything higher than that would flip the sign bit
372 // and we'd no longer be able to do signed comparisons to check
373 // for read vs. write.
376 // lower #read locks #write locks (from same thread)
377 // higher 0x0000 thread id of thread holding lock
379 // Adapted from LKRHash's lock.cpp, from GeorgeRe
380 // The original implementation is due to PALarson.