Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Spinning, Thread Ownership, and Recursion

Because transitions into the kernel incur such a big performance hit and threads tend to hold on to a lock for very short periods of time, an application’s overall performance can be improved by having a thread spin in user mode for a little while before having the thread transition to kernel mode. If the lock that the thread is waiting for becomes available while spinning, then the transition to kernel mode is avoided.

In addition, some locks impose a limitation where the thread that acquires the lock must be the thread that releases the lock. And some locks allow the currently owning thread to own the lock


recursively. The Mutex lock is an example of a lock that has these characteristics.1 Using some fancy logic, it is possible to build a hybrid lock that offers spinning, thread ownership, and recursion. Here is what the code looks like.

 

internal sealed class AnotherHybridLock : IDisposable {

// The Int32 is used by the primitive user­mode constructs (Interlocked methods) private Int32 m_waiters = 0;

 

// The AutoResetEvent is the primitive kernel­mode construct private AutoResetEvent m_waiterLock = new AutoResetEvent(false);

 

// This field controls spinning in an effort to improve performance private Int32 m_spincount = 4000; // Arbitrarily chosen count

 

// These fields indicate which thread owns the lock and how many times it owns it private Int32 m_owningThreadId = 0, m_recursion = 0;

 

public void Enter() {

// If calling thread already owns the lock, increment recursion count and return Int32 threadId = Thread.CurrentThread.ManagedThreadId;

if (threadId == m_owningThreadId) { m_recursion++; return; }

 

// The calling thread doesn't own the lock, try to get it SpinWait spinwait = new SpinWait();

for (Int32 spinCount = 0; spinCount < m_spincount; spinCount++) {

// If the lock was free, this thread got it; set some state and return if (Interlocked.CompareExchange(ref m_waiters, 1, 0) == 0) goto GotLock;

 

// Black magic: give other threads a chance to run

// in hopes that the lock will be released spinwait.SpinOnce();

}

 

// Spinning is over and the lock was still not obtained, try one more time if (Interlocked.Increment(ref m_waiters) > 1) {

// Still contention, this thread must wait m_waiterLock.WaitOne(); // Wait for the lock; performance hit

// When this thread wakes, it owns the lock; set some state and return

}

 

GotLock:

// When a thread gets the lock, we record its ID and

// indicate that the thread owns the lock once m_owningThreadId = threadId; m_recursion = 1;

}

 

public void Leave() {

// If the calling thread doesn't own the lock, there is a bug Int32 threadId = Thread.CurrentThread.ManagedThreadId;

if (threadId != m_owningThreadId)

throw new SynchronizationLockException("Lock not owned by calling thread");

 

 

 
 

1 Threads do not spin when waiting on a Mutex object because the Mutex’s code is in the kernel. This means that the thread had to have already transitioned into the kernel to check the Mutex’s state.




// Decrement the recursion count. If this thread still owns the lock, just return if (­­m_recursion > 0) return;

 

m_owningThreadId = 0; // No thread owns the lock now

 

// If no other threads are waiting, just return if (Interlocked.Decrement(ref m_waiters) == 0)

return;

 

// Other threads are waiting, wake 1 of them m_waiterLock.Set(); // Bad performance hit here

}

 

public void Dispose() { m_waiterLock.Dispose(); }

}

 

As you can see, adding extra behavior to the lock increases the number of fields it has, which in- creases its memory consumption. The code is also more complex, and this code must execute, which decreases the lock’s performance. In Chapter 29’s “Event Constructs” section, I compared the perfor- mance of incrementing an Int32 without any locking, with a primitive user-mode construct, and with a kernel-mode construct. I repeat the results of those performance tests here and I include the results of using the SimpleHybridlock and the AnotherHybridLock. The results are in fastest to slowest order.

 

Incrementing x: 8 Fastest

Incrementing x in M: 69 ~9x slower

Incrementing x in SpinLock: 164 ~21x slower

Incrementing x in SimpleHybridLock: 164 ~21x slower (similar to SpinLock) Incrementing x in AnotherHybridLock: 230 ~29x slower (due to ownership/recursion) Incrementing x in SimpleWaitLock: 8,854 ~1,107x slower

 

It is worth noting that the AnotherHybridLock hurts performance as compared to using the SimpleHybridLock. This is due to the additional logic and error checking required managing the thread ownership and recursion behaviors. As you see, every behavior added to a lock impacts its performance.

 

 


Date: 2016-03-03; view: 772


<== previous page | next page ==>
Nbsp;   A Simple Hybrid Lock | TheMonitor Class and Sync Blocks
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.007 sec.)