.NET 一些锁的原理与TASLock自旋锁的关键实现

锁一般分为以下六种类型 它们面向的领域都不同 善用它们你的程序可以拥有更好的并发性能 但如果乱用 那么你就必须要承担相应的技术债务;

1. 内核锁 (Kernel lock)

2. 颗粒锁 (Particle lock)

3. 自旋锁(Spin lock)

4. 分布式锁 (Distributed lock)

5. 轻量级锁 (Lightweight lock)

6. 偏向锁(Biased Locking)

每次锁面向的解决问题的场景是不同的,颗粒锁、分布式锁、在.NET中是需要开发人员自己造的;颗粒锁面向不同的场景所表现的形式是不同的;它主要的目标是尽可能的使用最小锁;它需要结合多种不同的锁实现;严格意义上是不存在颗粒锁的

lock关键字(语法糖)是对于Monitor的封装,而对于Monitor内的加锁与退出锁是由CLR实现的;从理论上讲Monitor是基于CLR对于EnterCriticalSection,LeaveCriticalSection,WaitForSingleObject,WaitForMultipleObjects等函数的封装;这么说法一半正确一半错误;若真的只是对其封装那么何必要由CLR实现?直接在C#定义好二进制对其的CRITICAL_SECTION结构体;调用P/Invoke即可实现;又何必如此费心神呢?

若用户主动使用Monitor::Enter请求进入临界区且同时加锁;一般CLR层会做以下一些操作;先检查当前线程是否具备获取到加锁标志;若具备则采取偏向锁对于锁的引用计数加一。(偏向锁)

若当前线程不具备加锁的标志;则开始竞争加锁的权限;若竞争成功设置当前线程的加锁标志;否则若无法竞争到则在一小段时钟周期内阻塞当前线程自旋(一般是原子信号自旋等算法锁);但不可能无限制的自旋;此时依据自旋的迭代次数或时钟周期退出自旋(轻量级锁);向下膨胀为“临界区-内核锁”;而且实现类似这个锁的方法;不会很复杂(C/C++基础设施层方面可自行实现)

分布式锁目前主要用于对分布式共享资源进行加锁;Redis具备相关的实现;但我在本文中对分布式锁并不会说的太细;但它实现起来不会太难;但至于分布式锁写得好不好那又是另一回事,这需要看写得人的技术水平包括锁的理解深度;

比如当我希望保证在分分布式环境下对某个资源的分布式安全处理那么我需要对目标的资源进行加锁;否则它可能会出现数

错误的问题;那么我只需要利用Socket向目标缓存服务器节点发送我需要对XX资源加锁;对应缓存服务器对目标资源进行加锁;

并设置请求加锁并获取到锁的客户端的原子标志;若其它客户端向服务器请求获取锁;那么服务器检查存在加锁的原子标志后告

知客户端你需要等待锁的获取并加入此客户到等待获取锁的pending poll;而请求获取锁的客户端得知后则先自行“自旋”等待分布式

锁的获取;当客户端在自旋了一定时间后还是无法获取到分布式锁;则进入“轻量级锁”;还是不可;则进入“内核信号”等待锁;在达

到一定时间后客户端获取失败;但若服务器或已获取锁的客户端宕机那么另外请求获取锁客户端则可能进入死锁状态;那么则会引

服务器集群系统的崩溃。针对于这类情况客户端会在一定时钟周期内脱离等待锁的情况;同时服务器在发现获取锁的客户机发生

宕机(心跳检查一般在20~50ms/s)则释放目标客户机获取的锁;重新分派对应的锁至pending poll内请求获取目标锁的客户端;

客户端若检查发生异常时主要脱离被锁的状态;并抛出异常信息。

自旋锁的实现一般采取原子信号的形式;注意原子信号的替换与自增自减是不需要从用户态进入内核态挂起当前线程且保存线

程上下文;获取到锁后在由内核态退出恢复线程的上下文;唤醒。线程上下文一般包含当前执行的ASM-code位置EIP、DR0~7、

AX、DX、CX、BX、SP、BP、SI、DI、DS、ES、CS、SS、FS、GS、REGISTER VAR ITEM等;

恢复线程上下文则操作系统需要从Working内存(一般在虚拟内存内)中拷出保存下的线程上下文各个设置;重置CPU的相关

寄存器包括汇编执行位置;在此需要花费的时钟周期的代价是昂贵的;它不是指内存压力而是CPU与内核调度器的压力;而且挂起

与恢复线程上下文将破坏CPU对于指令命中缓存,CPU硬件优化

自 .NET4.0 开始;微软在.NET/FCL中引入自旋锁(SpinLock)与 Thread::SpinWait 函数;一般自旋锁只能用于需要阻塞很短

的时间就可以获取锁的场景;一般主要用于操作 字典(散列)、链表、等不需要CPU处理很长的部分;准确的说法是“临界区效能

短时钟周期适用”,多用于低于1ms处理的情况;若临界区内的代码处理效率太慢的话使用适合的Monitor、Mutex、Event等锁是最

佳选择;

那么.NET 4.0中提供的自旋锁是什么类型的呢?它是TTASLock而今天我给出的自旋锁关键实现是TASLock;两类锁从原理上是

相同的;都是基于原子信号,唯一不同之处在于TTASLock它更加强大;

自旋锁故名思意;它是利用自旋的方式阻塞当前线程;与内核锁需要挂起与恢复线程且频繁的让CPU从用户态转入内核态不同

,它不需要如此;它追求在短时间内消耗CPU的方式竞争锁的加锁的权限,而事实上在绝大部分情况下根本不需要使用内核锁;这

些场景根本不需要阻塞太长时间;在下方本人给出一个竞争锁的权限的实现

        public void Enter(bool* localTaken, long iterations)
        {
            if (localTaken == null)
            {
                throw new ArgumentNullException("localTaken");
            }
            if (*localTaken)
            {
                throw new ArgumentException("localTaken");
            }
            int count = 0;
            while(!*localTaken)
            {
                if (!(iterations == Infinite || count++ < iterations))
                {
                    break;
                }
                if (Interlocked.Increment(ref _signal) == 0x01) // 获取到锁信号
                {
                    *localTaken = true;
                }
            }
        }
上述代码它是一个标准的TTASLock的实现;原子递增原子信号 若具备相应的信号则跳出竞争锁;进入临界区,但考虑到可能出

现死锁的情况;它允许在一定数量的迭代后跳出竞争锁的状态(参考且借鉴了一部分微软的实现);但它未考虑偏向锁的问题;即在

相同的线程内重复使用锁的Enter会造成死锁。

列:在下方本人给出一个借鉴偏向锁与引用计数的思想进而改进的竞争锁的关键部分实现;它可以避免在相同线程内重复进入锁而造成线程死锁

        public void Enter(bool* localTaken, long iterations)
        {
            if (localTaken == null)
            {
                throw new ArgumentNullException("localTaken");
            }
            if (*localTaken)
            {
                throw new ArgumentException("localTaken");
            }
            int threadid = Thread.CurrentThreadId;
            if (threadid == Interlocked.CompareExchange(ref _threadid, 0x00, 0x00))
            {
                *localTaken = true;
            }
            else
            {
                long count = 0;
                while (!*localTaken)
                {
                    if (!(iterations == Infinite || count++ < iterations))
                    {
                        break;
                    }
                    if (Interlocked.Increment(ref _signal) == 0x01) // 获取到锁信号
                    {
                        *localTaken = true;
                        Interlocked.Exchange(ref _threadid, threadid);
                    }
                }
            }
            if (*localTaken)
            {
                Interlocked.Increment(ref _refcount);
            }
        }



猜你喜欢

转载自blog.csdn.net/liulilittle/article/details/78938632