多线程中的锁系统(四)-谈谈自旋锁

目录

一:基础

二:自旋锁示例

三:SpinLock

四:继续SpinLock

五:总结

一:基础

内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式。用户模式构造和内核模式构造

优点:cpu利用最大化。它发现资源被锁住,请求就排队等候。线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求。

缺点:托管代码->用户模式代码->内核代码损耗、线程上下文切换损耗。

在锁的时间比较短时,系统频繁忙于休眠、切换,是个很大的性能损耗。

自旋锁:原子操作+自循环。通常说的用户构造模式。  线程不休眠,一直循环尝试对资源访问,直到可用。

优点:完美解决内核锁的缺点。

缺点:长时间一直循环会导致cpu的白白浪费,高并发竞争下、CPU的消耗特别严重。

混合锁:内核锁+自旋锁。 混合锁是先自旋锁一段时间或自旋多少次,再转成内核锁。

优点:内核锁和自旋锁的折中方案,利用前二者优点,避免出现极端情况(自旋时间过长,内核锁时间过短)。

缺点: 自旋多少时间、自旋多少次,这些策略很难把控。

ps:操作系统或net框架,这块算法策略做的已经非常优了,有些API函数也提供了时间及次数可配置项,让开发者根据需求自行判断。

二:自旋锁示例

来看下我们自己简单实现的自旋锁:

        int signal = 0;
            var li = new List<int>();
            Parallel.For(0, 1000 * 10000, r =>
            {
                while (Interlocked.Exchange(ref signal, 1) != 0)//加自旋锁
                {
                    //黑魔法
                }
                li.Add(r);
                Interlocked.Exchange(ref signal, 0);  //释放锁
            });
            Console.WriteLine(li.Count);
            //输出:10000000

上面就是自旋锁:Interlocked.Exchange+while

1:定义signal  0可用,1不可用。

2:Parallel模拟并发竞争,原子更改signal状态。 后续线程自旋访问signal,是否可用。

3:A线程使用完后,更改signal为0。 剩余线程竞争访问资源,B线程胜利后,更改signal为1,失败线程继续自旋,直到可用。

三:SpinLock

SpinLock是net4.0后系统帮我们实现的自旋锁,内部做了优化。

简单看下实例:

  var li = new List<int>();
            var sl = new SpinLock();
            Parallel.For(0, 1000 * 10000, r =>
            {
                bool gotLock = false;     //释放成功
                sl.Enter(ref gotLock);    //进入锁
                li.Add(r);
                if (gotLock) sl.Exit();  //释放
            });
            Console.WriteLine(li.Count);
            //输出:10000000

四:继续SpinLock

new SpinLock(false)   这个构造函数主要用来帮我们检查死锁用,true是开启。

开启状态下,如果发生死锁会直接抛异常的。

贴了一部分源码(已折叠),我们来看下:

  public void Enter(ref bool lockTaken)
        {
            if (lockTaken)
            {
                lockTaken = false;
                throw new System.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException"));
            }

            // Fast path to acquire the lock if the lock is released
            // If the thread tracking enabled set the new owner to the current thread id
            // Id not, set the anonymous bit lock
            int observedOwner = m_owner;
            int newOwner = 0;
            bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0;
            if (threadTrackingEnabled)
            {
                if (observedOwner == LOCK_UNOWNED)
                    newOwner = Thread.CurrentThread.ManagedThreadId;
            }
            else if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
            {
                newOwner = observedOwner | LOCK_ANONYMOUS_OWNED; // set the lock bit
            }
            if (newOwner != 0)
            {
#if !FEATURE_CORECLR
                Thread.BeginCriticalRegion();
#endif 

#if PFX_LEGACY_3_5
                if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
                {
                    lockTaken = true;
                    return;
                }
#else
                if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                {
                    // Fast path succeeded
                    return;
                }
#endif
#if !FEATURE_CORECLR
                Thread.EndCriticalRegion();
#endif
            }
            //Fast path failed, try slow path
            ContinueTryEnter(Timeout.Infinite, ref lockTaken);
        }
private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
        {
            long startTicks = 0;
            if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
            {
                startTicks = DateTime.UtcNow.Ticks;
            }

#if !FEATURE_PAL && !FEATURE_CORECLR   // PAL doesn‘t support  eventing, and we don‘t compile CDS providers for Coreclr
            if (CdsSyncEtwBCLProvider.Log.IsEnabled())
            {
                CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner);
            }
#endif 

            if (IsThreadOwnerTrackingEnabled)
            {
                // Slow path for enabled thread tracking mode
                ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTicks, ref lockTaken);
                return;
            } 

            // then thread tracking is disabled
            // In this case there are three ways to acquire the lock
            // 1- the first way the thread either tries to get the lock if it‘s free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2
            // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn
            // the late the thread arrives the more it spins and less frequent it check the lock avilability
            // Also the spins count is increaes each iteration
            // If the spins iterations finished and failed to acquire the lock, go to step 3
            // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
            // If the timeout is expired in after step 1, we need to decrement the waiters count before returning

            int observedOwner;

            //***Step 1, take the lock or update the waiters

            // try to acquire the lock directly if possoble or update the waiters count
            SpinWait spinner = new SpinWait();
            while (true)
            {
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
#if !FEATURE_CORECLR
                    Thread.BeginCriticalRegion();
#endif

#if PFX_LEGACY_3_5
                    if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner) == observedOwner)
                    {
                        lockTaken = true;
                        return;
                    }
#else
                    if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
                    {
                        return;
                    }
#endif 

#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
#endif
                }
                else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow
                    if ((observedOwner & WAITERS_MASK) ==  MAXIMUM_WAITERS || Interlocked.CompareExchange(ref m_owner, observedOwner + 2, observedOwner) == observedOwner)
                        break;

                spinner.SpinOnce();
            }

            // Check the timeout.
            if (millisecondsTimeout == 0 ||
                (millisecondsTimeout != Timeout.Infinite &&
                TimeoutExpired(startTicks, millisecondsTimeout)))
            {
                DecrementWaiters();
                return;
            }

            //***Step 2. Spinning
            //lock acquired failed and waiters updated
            int turn = ((observedOwner + 2) & WAITERS_MASK) / 2;
            int processorCount = PlatformHelper.ProcessorCount;
            if (turn < processorCount)
            {
                int processFactor = 1;
                for (int i = 1; i <= turn * SPINNING_FACTOR; i++)
                {
                    Thread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor);
                    if (processFactor < processorCount)
                        processFactor++;
                    observedOwner = m_owner;
                    if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                    {
#if !FEATURE_CORECLR
                        Thread.BeginCriticalRegion();
#endif

                        int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                            observedOwner | 1 // don‘t decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                            : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
                        Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#if PFX_LEGACY_3_5
                        if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
                        {
                            lockTaken = true;
                            return;
                        }
#else
                        if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                        {
                            return;
                        }
#endif

#if !FEATURE_CORECLR
                        Thread.EndCriticalRegion();
#endif
                    }
                }
            } 

            // Check the timeout.
            if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
            {
                DecrementWaiters();
                return;
            } 

            //*** Step 3, Yielding
            //Sleep(1) every 50 yields
            int yieldsoFar = 0;
            while (true)
            {
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
#if !FEATURE_CORECLR
                    Thread.BeginCriticalRegion();
#endif
                    int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                           observedOwner | 1 // don‘t decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                           : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
                    Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#if PFX_LEGACY_3_5
                    if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner)
                    {
                        lockTaken = true;
                        return;
                    }
#else
                    if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                    {
                        return;
                    }
#endif

#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
#endif
                } 

                if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
                {
                    Thread.Sleep(1);
                }
                else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
                {
                    Thread.Sleep(0);
                }
                else
                {
#if PFX_LEGACY_3_5
                    Platform.Yield();
#else
                    Thread.Yield();
#endif
                }

                if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
                {
                    //Check the timeout.
                    if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
                    {
                        DecrementWaiters();
                        return;
                    }
                } 

                yieldsoFar++;
            }
        }

        /// <summary>
        /// decrements the waiters, in case of the timeout is expired
        /// </summary>
        private void DecrementWaiters()
        {
            SpinWait spinner = new SpinWait();
            while (true)
            {
                int observedOwner = m_owner;
                if ((observedOwner & WAITERS_MASK) == 0) return; // don‘t decrement the waiters if it‘s corrupted by previous call of Exit(false)
                if (Interlocked.CompareExchange(ref m_owner, observedOwner - 2, observedOwner) == observedOwner)
                {
                    Contract.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
                    break;
                }
                spinner.SpinOnce();
            }

        }

从代码中发现SpinLock并不是我们简单的实现那样一直自旋,其内部做了很多优化。

1:内部使用了Interlocked.CompareExchange保持原子操作, m_owner 0可用,1不可用。

2:第一次获得锁失败后,继续调用ContinueTryEnter,ContinueTryEnter有三种获得锁的情况。

3:ContinueTryEnter函数第一种获得锁的方式。 使用了while+SpinWait,后续再讲。

4:第一种方式达到最大等待者数量后,命中走第二种。 继续自旋 turn * 100次。100这个值是处理器核数(4, 8 ,16)下最好的。

5:第二种如果还不能获得锁,走第三种。   这种就有点混合构造的意味了,如下:

    if (yieldsoFar % 40 == 0)
                    Thread.Sleep(1);
                else if (yieldsoFar % 10 == 0)
                    Thread.Sleep(0);
                else
                    Thread.Yield();

Thread.Sleep(1) : 终止当前线程,放弃剩下时间片 休眠1毫秒。 退出跟其他线程抢占cpu。当然这个一般会更多,系统无法保证这么细的时间粒度。

Thread.Sleep(0):  终止当前线程,放弃剩下时间片。  但立马还会跟其他线程抢cpu,能不能抢到跟线程优先级有关。

Thread.Yeild():       结束当前线程。让出cpu给其他准备好的线程。其他线程ok后或没有准备好的线程,继续执行。 跟优先级无关。

Thread.Yeild()还会返回个bool值,是否让出成功。

从源码中,我们可以学到不少编程技巧。 比如我们也可以使用  自旋+Thread.Yeild()   或 while+Thread.Yeild() 等组合。

五:总结

本章谈了自旋锁的基础+楼主的经验。  SpinLock类源码这块,只粗浅理解了下,并没有深究。

测了下SpinLock和自己实现的自旋锁性能对比(并行添加1000w List<int>()),SpinLock是单纯的自旋锁性能2倍以上。

还测了下lock的性能,是系统SpinLock性能的3倍以上。  可见lock内部自旋的效率更高,可惜看不到monitor.enter CLR实现的代码。

参考资源

http://www.projky.com/dotnet/4.0/System/Threading/SpinLock.cs.html

作者:蘑菇先生   出处:http://www.cnblogs.com/mushroom/p/4245529.html

时间: 2024-10-12 19:59:51

多线程中的锁系统(四)-谈谈自旋锁的相关文章

多线程的那点儿事(之自旋锁)

自旋锁是SMP中经常使用到的一个锁.所谓的smp,就是对称多处理器的意思.在工业用的pcb板上面,特别是服务器上面,一个pcb板有多个cpu是 很正常的事情.这些cpu相互之间是独立运行的,每一个cpu均有自己的调度队列.然而,这些cpu在内存空间上是共享的.举个例子说,假设有一个数据 value = 10,那么这个数据可以被所有的cpu访问.这就是共享内存的本质意义. 我们可以看一段Linux 下的的自旋锁代码(kernel 2.6.23,asm-i386/spinlock.h),就可有清晰的

自旋锁原理及java自旋锁

转载:http://blog.csdn.net/sunp823/article/details/49886051 锁的状态:无锁状态.偏向锁状态.轻量级锁状态.重量级锁状态. 偏向锁适用于只有一个线程访问同步块的场景. 轻量级锁的,竞争的线程不会阻塞,适用于持有锁的时间比较短.没有竞争到的线程会自旋来获取锁(自旋的次数这个倒不是非常确定,需要看hotspot源代码来查看,从jni.cpp的monitorenter函数入手),获取失败的话,轻量级锁会膨胀为重量级锁,引起阻塞. 一.自旋锁的概念首先

多线程之美6一CAS与自旋锁

1.什么是CAS CAS 即 compare and swap 比较并交换, 涉及到三个参数,内存值V, 预期值A, 要更新为的值B, 拿着预期值A与内存值V比较,相等则符合预期,将内存值V更新为B, 不相等,则不能更新V. 为什么预期值A与内存值V不一样了呢? 在多线程环境下,对于临界区的共享资源,所有线程都可以访问修改,这时为了保证数据不会发生错误,通常会对访问临界区资源加锁,同一时刻最多只能让一个线程访问(独占模式下),这样会让线程到临界区时串行执行,加锁操作可能会导致并发性能降低,而循环

虚拟机中的锁优化简介(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)

高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimination).锁膨胀(Lock Coarsening).轻量级锁(Lightweight Locking).偏向锁(Biased Locking)等,这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率. 13.3.1 自旋锁与自适应自旋 前面我们讨论互斥同步的时候,

JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀

详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt364 本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力.而在很多应用上,共享数据的锁定状态只会持续很短的一段时间.若实体机上有多个处理器,能让两个以上的线程同时并行执行,我们就可以让后面请求锁的那个线程原地自旋(不放弃CPU时间),看

iOS - 互斥锁&amp;&amp;自旋锁 多线程安全隐患(转载)

一.多线程安全隐患 资源共享  一块资源可能会被多个线程共享,也就是多个线程可能会访问到一块资源 比如多个线程访问同一个对象,同一个变量,同一个文件. 当多线程访问同一块资源的时候,很容易引发数据错乱和数据安全问题二.原子和非原子属性 1>OC 在定义属性的时候有nonatomic和atomic两种选择      * atomic:原子属性,为 setter 方法加锁      * nonatomic:非原子属性,不会为 setter 方法加锁        普通情况下都是在主线程做操作,所以一

大话Linux内核中锁机制之原子操作、自旋锁

转至:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,

多线程编程之自旋锁

一.什么是自旋锁 一直以为自旋锁也是用于多线程互斥的一种锁,原来不是! 自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制.自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用.无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁.但是两者在调度机制上略有不同.对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态.但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁

抢占式内核与非抢占式内核中的自旋锁(spinlock)的区别

一.概括 (1)自旋锁适用于SMP系统,UP系统用spinlock是作死. (2)保护模式下禁止内核抢占的方法:1.执行终端服务例程时2.执行软中断和tasklet时3.设置本地CPU计数器preempt_count (3)自旋锁的忙等待的实际意义是:尝试获取自旋锁的另一个进程不断尝试获取被占用的自旋锁,中间只pause一下! (4)在抢占式内核的spin_lock宏中,第一次关抢占,目的是防止死锁(防止一个已经获取自旋锁而未释放的进程被抢占!!).而后又开抢占,目的是让已经释放自旋锁的进程可以