zoukankan      html  css  js  c++  java
  • 自旋锁

    自旋锁

    目录

    一:基础

    二:自旋锁示例

    三: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

  • 相关阅读:
    Codeforces467C George and Job
    Codeforces205E Little Elephant and Furik and RubikLittle Elephant and Furik and Rubik
    Codeforce205C Little Elephant and Interval
    51nod1829 函数
    51nod1574 排列转换
    nowcoder35B 小AA的数列
    Codeforce893E Counting Arrays
    gym101612 Consonant Fencity
    CodeForces559C Gerald and Giant Chess
    CodeForces456D A Lot of Games
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4247945.html
Copyright © 2011-2022 走看看