[C#Thread] SpinLock lock synchronization lock

Posted by turdferguson on Mon, 03 Jan 2022 13:00:43 +0100

SpinLock Lock

Keywords: Mutex, spin, non-reentrant, very short usage time

If you open four threads, ranging from 0 to 10 million, this program runs on a 4-core cpu with an interlock lock, as shown below:

Thread 1 acquires the lock, and all other threads that do not acquire the lock are spinning (dead-loop) and occupying the core. So make sure that interLock locks don't hold locks for more than a very short period of time for any thread. Otherwise, it would cause a huge waste of resources.

SpinLock uses spinWait and InterLocked internally for atomic operations.

Core Code:

private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
        {
            Debug.Assert(IsThreadOwnerTrackingEnabled);

            const int LockUnowned = 0;
            // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
            // We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
            int newOwner = Environment.CurrentManagedThreadId;
            if (_owner == newOwner)
            {
                // We don't allow lock recursion. Duplicate locking is not allowed, which is why this lock is not reentrant
                throw new LockRecursionException(SR.SpinLock_TryEnter_LockRecursionException);
            }

            SpinWait spinner = default;

            // Loop until the lock has been successfully acquired or, if specified, the timeout expires.
            while (true)
            {
                // We failed to get the lock, either from the fast route or the last iteration
                // and the timeout hasn't expired; spin once and try again.
               ///Threads are spinned here.
                spinner.SpinOnce();
 
                // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.

                if (_owner == LockUnowned)
                {
                    if (CompareExchange(ref _owner, newOwner, LockUnowned, ref lockTaken) == LockUnowned)
                    {
                        return;
                    }
                }
                // Check the timeout.  We only RDTSC if the next spin will yield, to amortize the cost.
                if (millisecondsTimeout == 0 ||
                    (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
                    TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
                {
                    return;
                }
            }
        }

 

Principle: Lock the internal SpinWait.SpinOnce. After more than 10 spins, each spin triggers context switching, followed by a sleep(0) operation every five spins and a sleep(1) operation every 20.
Sleep(0) only allows threads with equal or higher priority to use the current CPU, while other threads can only wait to starve. If there is no suitable thread, the current thread will reuse the CPU time slice.

 


Key points for use:
1. Each use is initialized to false to ensure that it is not acquired, true if a lock has been acquired, false otherwise. (
2. SpinLock is a non-reentrant lock, which means that if a thread holds a lock, it is not allowed to enter the lock again.
3. The SpinLock structure is a low-level mutually exclusive synchronization primitive that rotates while waiting to acquire a lock.
4. When using SpinLock, make sure that no thread holds locks for more than a very short period of time and that no thread is blocked when holding locks.
5. Even if SpinLock does not acquire a lock, it also generates a time slice for the thread. Threads that do not acquire a lock at this time are waiting for other core s that occupy the cpu to release the lock.
6. On multicore computers, SpinLock will perform better than other types of locks when wait times are expected to be shorter and contention is rare.
7. Since SpinLock is a value type, if you want both copies to reference the same lock, you must explicitly pass the lock by reference.
8. SpinLock may be corrupted if Exit does not have the Enter internal state that was called first.
9. If thread ownership tracking is enabled (via) whether it can be used with IsThreadOwnerTrackingEnabled, an exception will be thrown when a thread attempts to reenter a lock it already holds. However, if thread ownership tracking is disabled, attempting to enter a lock already held will result in a deadlock.


Attribute Description
IsHeld * Gets whether the lock is currently occupied by any thread.
IsHeldByCurrentThread Gets whether the lock has been occupied by the current thread.
IsThreadOwnerTrackingEnabled Gets whether thread ownership tracking is enabled for this instance.

Method) Description
Enter(Boolean) Acquires locks in a reliable way so that lockTaken can be checked in a reliable way to determine if a lock has been acquired, even if an exception occurs in a method call.
Exit() Release the lock.
Exit(Boolean) Release the lock.
TryEnter(Boolean) Attempts to acquire locks in a reliable manner so that lockTaken can be checked reliably to determine if a lock has been acquired, even if an exception occurs in a method call
TryEnter(Int32, Boolean) tries to acquire locks in a reliable way so that lockTaken can be checked reliably to determine if a lock has been acquired, even if an exception occurs in a method call.
TryEnter(TimeSpan, Boolean) tries to acquire locks in a reliable way so that lockTaken can be checked reliably to determine if a lock has been acquired, even if an exception occurs in a method call.
 

Case:

Open 4 threads from 0 to 10 million

using System.Diagnostics;

class Program
{
    static   long counter = 1;
    //Declaring it as a read-only field causes one to be returned for each call SpinLock New copy,
    //Under multithreading, each method succeeds in acquiring locks, and protected critical zones are not serialized as expected.
    static SpinLock sl = new();//A class requests a lock for multithreading and cannot be declared read-only.

    // Open 4 threads from 0 to 10 million
    static void Main(string[] args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
       Parallel.Invoke(f1, f1, f1, f1);
      Console.WriteLine(stopwatch.ElapsedMilliseconds);
        Console.WriteLine(counter);

    }
    static void f1()
    {
    
        for (int i = 1; i <= 25_000_00; i++)
        {
          
            // static SpinLock sl = new();Error declaration so that each thread gets a lock, resulting in a loss of synchronization
            bool dfdf = false;//Initialize every use to false,Each cycle starts fighting for locks.
            sl.Enter(ref dfdf);
            try
            {
                counter++;
            
            }
            finally
            {

                sl.Exit();
            }
         
       
        }
    }

}

 

Topics: C#