• 自旋锁


    自旋锁

    目录

    一:基础

    二:自旋锁示例

    三: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是开启。

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

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

     View Code

    从代码中发现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

  • 相关阅读:
    NSPrediccate 查询
    集合 不可变集合
    集合 不可变
    考核题 7
    考核题 6
    考核题 4
    练习题12
    练习题3
    iOS 实现在string任意位置添加新的表情
    在 ZBarSDK 中使用Block回调传值 Block在扫描成功后 变为空
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4247945.html
Copyright © 2020-2023  润新知