自旋锁 订阅
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。 展开全文
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。
信息
外文名
Spin lock
概    念
保护共享资源
中文名
自旋锁
初    衷
在短期间内进行轻量级的锁定
自旋锁概念
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
收起全文
精华内容
下载资源
问答
  • golang使用redis的setnx实现了一个自选,有key超时,同时也有我们调用redis链接时的超时。 package locker import ( context github.com/go-redis/redis runtime time ) type Lock struct { resource string...
  • 主要给大家介绍了关于我们说的CAS自旋锁到底是什么的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 一、自旋锁 自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问。与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁。当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处于...
  • 主要介绍了C#多线程编程中的锁系统(四):自旋锁,本文讲解了基础知识、自旋锁示例、SpinLock等内容,需要的朋友可以参考下
  • 本文章是关于信号量、互斥体和自旋锁的区别。
  • 文章目录锁---自旋锁 VS 适应性自旋锁自旋锁1、概念:2、提出背景3、自旋锁的原理4、 自旋锁的优缺点5、自旋锁开启自适应自旋锁总结 自旋锁 1、概念: 当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被...

    锁9—自旋锁 VS 适应性自旋锁

    ************ 如有侵权请提示删除 ***************



    自旋锁

    1、概念:

    当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)

    2、提出背景

    由于在多处理器环境中某些资源的有限性,有时需要互斥访问(mutual exclusion),这时候就需要引入锁的概念,只有获取了锁的线程才能够对资源进行访问,由于多线程的核心是CPU的时间分片,所以同一时刻只能有一个线程获取到锁。那么就面临一个问题,那么没有获取到锁的线程应该怎么办?

    通常有两种处理方式:一种是没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁,这种锁叫做自旋锁,它不用将线程阻塞起来(NON-BLOCKING);还有一种处理方式就是把自己阻塞起来,等待重新调度请求,这种叫做互斥锁。

    3、自旋锁的原理

    自旋锁的原理比较简单,如果持有锁的线程能在短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞状态,它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户进程和内核切换的消耗。

    因为自旋锁避免了操作系统进程调度和线程切换,所以自旋锁通常适用在时间比较短的情况下。由于这个原因,操作系统的内核经常使用自旋锁。但是,如果长时间上锁的话,自旋锁会非常耗费性能,它阻止了其他线程的运行和调度。线程持有锁的时间越长,则持有该锁的线程将被 OS(Operating System) 调度程序中断的风险越大。如果发生中断情况,那么其他线程将保持旋转状态(反复尝试获取锁),而持有该锁的线程并不打算释放锁,这样导致的是结果是无限期推迟,直到持有锁的线程可以完成并释放它为止。

    解决上面这种情况一个很好的方式是给自旋锁设定一个自旋时间,等时间一到立即释放自旋锁。自旋锁的目的是占着CPU资源不进行释放,等到获取锁立即进行处理。但是如何去选择自旋时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU 资源,进而会影响整体系统的性能。因此自旋的周期选的额外重要!JDK在1.6 引入了适应性自旋锁,适应性自旋锁意味着自旋时间不是固定的了,而是由前一次在同一个锁上的自旋时间以及锁拥有的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间。

    watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F4aWFvYm9nZQ==,size_16,color_FFFFFF,t_70

    4、 自旋锁的优缺点

    优点:自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!

    缺点:但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cpu 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁。

    5、自旋锁开启

    虽然在JDK1.4.2的时候就引入了自旋锁,但是需要使用“-XX:+UseSpinning”参数来开启。在到了JDK1.6以后,就已经是默认开启了。

    举个栗子:

    public class SpinLockTest {
        /**
         * 持有锁的线程,null表示锁未被线程持有
         */
        private AtomicReference<Thread> ref = new AtomicReference<>();
        public void lock(){
            Thread currentThread = Thread.currentThread();
            while(!ref.compareAndSet(null, currentThread)){
                //当ref为null的时候compareAndSet返回true,反之为false
                //通过循环不断的自旋判断锁是否被其他线程持有
            }
        }
        public void unLock() {
            Thread cur = Thread.currentThread();
            if(ref.get() != cur){
                //exception ...
            }
            ref.set(null);
        }
    }
    
    
    //自旋锁测试
    public class SpinLockTestTest {
        static int count = 0;
    
        @Test
        public void spinLockTest() throws InterruptedException {
            ExecutorService executorService = Executors.newFixedThreadPool(100);
            CountDownLatch countDownLatch = new CountDownLatch(100);
            SpinLockTest2 spinLockTest2 = new SpinLockTest2();
            for (int i = 0; i < 100; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        spinLockTest2.lock();
                        ++count;
                        spinLockTest2.unLock();
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await();
            System.out.println (count);
       }
    }
    

    通过上面的代码可以看出,自旋就是在循环判断条件是否满足,那么会有什么问题吗?如果锁被占用很长时间的话,自旋的线程等待的时间也会变长,白白浪费掉处理器资源。因此在JDK中,自旋操作默认10次,我们可以通过参数“-XX:PreBlockSpin”来设置,当超过来此参数的值,则会使用传统的线程挂起方式来等待锁释放。

    自适应自旋锁

    随着JDK的更新,在1.6的时候,又出现了一个叫做“自适应自旋锁”的玩意。它的出现使得自旋操作变得聪明起来,不再跟之前一样死板。所谓的“自适应”意味着对于同一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来确定的。例如对于A锁对象来说,如果一个线程刚刚通过自旋获得到了锁,并且该线程也在运行中,那么JVM会认为此次自旋操作也是有很大的机会可以拿到锁,因此它会让自旋的时间相对延长。但是如果对于B锁对象自旋操作很少成功的话,JVM甚至可能直接忽略自旋操作。因此,自适应自旋锁是一个更加智能,对我们的业务性能更加友好的一个锁。

    总结

    自旋锁是为了提高资源的使用频率而出现的一种锁,自旋锁说的是线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。

    自旋锁在等待期间不会睡眠或者释放自己的线程。自旋锁不适用于长时间持有CPU的情况,这会加剧系统的负担,为了解决这种情况,需要设定自旋周期,那么自旋周期的设定也是一门学问。

    在自旋锁中 另有三种常见的锁形式:TicketLock、CLHlock和MCSlock

    这里直接举栗子:

    1. TicketLock 是一种同步机制或锁定算法,它是一种自旋锁,它使用ticket 来控制线程执行顺序。
    /**
     * @Description TicketLock 是一种同步机制或锁定算法,它是一种自旋锁,它使用ticket 来控制线程执行顺序。
     *
     * TicketLock 是基于先进先出(FIFO) 队列的机制。
     * 它增加了锁的公平性,其设计原则如下:TicketLock 中有两个 int 类型的数值,
     * 开始都是0,第一个值是队列ticket(队列票据), 第二个值是 出队(票据)。
     * 队列票据是线程在队列中的位置,而出队票据是现在持有锁的票证的队列位置。
     * 可能有点模糊不清,简单来说,就是队列票据是你取票号的位置,出队票据是你距离叫号的位置。
     *
     *
     * @Author zhoumm
     * @Version V1.0.0
     * @Since 1.0
     * @Date 2020-05-29
     */
    //这个设计是有问题的,因为获得自己的号码之后,是可以对号码进行更改的,这就造成系统紊乱,锁不能及时释放。
    //需要有一个能确保每个人按会着自己号码排队办业务的角色
    public class TicketLock {
        // 队列票据(当前排队号码)
        private AtomicInteger queueNum = new AtomicInteger();
    
        // 出队票据(当前需等待号码)
        private AtomicInteger dueueNum = new AtomicInteger();
    
        // 获取锁:如果获取成功,返回当前线程的排队号
        public int lock(){
            int currentTicketNum = dueueNum.incrementAndGet();
            while (currentTicketNum != queueNum.get()){
                // doSomething...
            }
            return currentTicketNum;
        }
    
        // 释放锁:传入当前排队的号码
        public void unLock(int ticketNum){
            queueNum.compareAndSet(ticketNum,ticketNum + 1);
        }
    
        //改进后,这就不再需要返回值,办业务的时候,要将当前的这一个号码缓存起来,在办完业务后,需要释放缓存的这条票据。
        //缺点:虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量queueNum ,
        // 每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。
        //为了解决这个问题,MCSLock 和 CLHLock 应运而生
        public class TicketLock2 {
    
            // 队列票据(当前排队号码)
            private AtomicInteger queueNum = new AtomicInteger();
    
            // 出队票据(当前需等待号码)
            private AtomicInteger dueueNum = new AtomicInteger();
    
            //线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
            private ThreadLocal<Integer> ticketLocal = new ThreadLocal<>();
    
            public void lock(){
                int currentTicketNum = dueueNum.incrementAndGet();
    
                // 获取锁的时候,将当前线程的排队号保存起来
                ticketLocal.set(currentTicketNum);
                while (currentTicketNum != queueNum.get()){
                    // doSomething...
                }
            }
    
            // 释放锁:从排队缓冲池中取
            public void unLock(){
                Integer currentTicket = ticketLocal.get();
                queueNum.compareAndSet(currentTicket,currentTicket + 1);
            }
    
        }
    }
    
    1. TicketLock 是基于队列的,那么 CLHLock 就是基于链表设计的
    /**
     * @Description
     * TicketLock 是基于队列的,那么 CLHLock 就是基于链表设计的
     *
     * CLH 是一种基于链表的可扩展,高性能,公平的自旋锁,申请线程只能在本地变量上自旋,
     * 它会不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
     *
     *
     * @Author zhoumm
     * @Version V1.0.0
     * @Since 1.0
     * @Date 2020-05-29
     */
    public class CLHLock {
        public static class CLHNode{
            private volatile boolean isLocked = true;
        }
    
        // 尾部节点
        private volatile CLHNode tail;
        private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<>();
        private static final AtomicReferenceFieldUpdater<CLHLock,CLHNode> UPDATER =
                AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,CLHNode.class,"tail");
    
        public void lock(){
            // 新建节点并将节点与当前线程保存起来
            CLHNode node = new CLHNode();
            LOCAL.set(node);
    
            // 将新建的节点设置为尾部节点,并返回旧的节点(原子操作),这里旧的节点实际上就是当前节点的前驱节点
            CLHNode preNode = UPDATER.getAndSet(this,node);
            if(preNode != null){
                // 前驱节点不为null表示当锁被其他线程占用,通过不断轮询判断前驱节点的锁标志位等待前驱节点释放锁
                while (preNode.isLocked){
    
                }
                preNode = null;
                LOCAL.set(node);
            }
            // 如果不存在前驱节点,表示该锁没有被其他线程占用,则当前线程获得锁
    
        }
    
        public void unlock() {
            // 获取当前线程对应的节点
            CLHNode node = LOCAL.get();
            // 如果tail节点等于node,则将tail节点更新为null,同时将node的lock状态职位false,表示当前线程释放了锁
            if (!UPDATER.compareAndSet(this, node, null)) {
                node.isLocked = false;
            }
            node = null;
        }
    }
    
    1. MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销
    /**
     * @Description
     * MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,
     * 直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
     *
     * 总结:
     * 1.都是基于链表,不同的是CLHLock是基于隐式链表,没有真正的后续节点属性,MCSLock是显示链表,有一个指向后续节点的属性。
     * 2.将获取锁的线程状态借助节点(node)保存,每个线程都有一份独立的节点,这样就解决了TicketLock多处理器缓存同步的问题。
     * @Author zhoumm
     * @Version V1.0.0
     * @Since 1.0
     * @Date 2020-05-29
     */
    public class MCSLock {
        public static class MCSNode {
            volatile MCSNode next;
            volatile boolean isLocked = true;
        }
    
        private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<>();
    
        // 队列
        @SuppressWarnings("unused")
        private volatile MCSNode queue;
    
        private static final AtomicReferenceFieldUpdater<MCSLock,MCSNode> UPDATE =
                AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,MCSNode.class,"queue");
    
        public void lock(){
            // 创建节点并保存到ThreadLocal中
            MCSNode currentNode = new MCSNode();
            NODE.set(currentNode);
    
            // 将queue设置为当前节点,并且返回之前的节点
            MCSNode preNode = UPDATE.getAndSet(this, currentNode);
            if (preNode != null) {
                // 如果之前节点不为null,表示锁已经被其他线程持有
                preNode.next = currentNode;
                // 循环判断,直到当前节点的锁标志位为false
                while (currentNode.isLocked) {
                }
            }
        }
    
        public void unlock() {
            MCSNode currentNode = NODE.get();
            // next为null表示没有正在等待获取锁的线程
            if (currentNode.next == null) {
                // 更新状态并设置queue为null
                if (UPDATE.compareAndSet(this, currentNode, null)) {
                    // 如果成功了,表示queue==currentNode,即当前节点后面没有节点了
                    return;
                } else {
                    // 如果不成功,表示queue!=currentNode,即当前节点后面多了一个节点,表示有线程在等待
                    // 如果当前节点的后续节点为null,则需要等待其不为null(参考加锁方法)
                    while (currentNode.next == null) {
                    }
                }
            } else {
                // 如果不为null,表示有线程在等待获取锁,此时将等待线程对应的节点锁状态更新为false,同时将当前线程的后继节点设为null
                currentNode.next.isLocked = false;
                currentNode.next = null;
            }
        }
    
    }
    

    参考:

    展开全文
  • 一、自旋锁  自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要...
  • golang 自旋锁的实现

    2020-09-19 22:10:30
    主要介绍了golang 自旋锁的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 自旋锁-rs Rust 中的自旋锁实现 建造 运行cargo build 用法 该库实现了 Reader/Writer 锁。 锁定共享读取访问的自旋锁时,您将获得对受保护数据的引用,而锁定独占写入访问时,您将获得可变引用。 extern crate ...
  • 学习地址:狂神Bilibili个人主页 1.什么是自学锁? 是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够...3.自定义自旋锁 import java.util.concurrent.atomic

    学习地址:狂神Bilibili个人主页

    1.什么是自学锁?

    是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

    2.使用场景

    就像你去银行办理业务先去取号,在没轮到你之前你必须等待;直到银行办理人员叫你去办理业务,到你办理业务的时候这个窗口只能是你这个号码办理,其他号码不能跟你一起在这个窗口办理;办完业务后,办理任务再呼下一个号码

    3.自定义自旋锁

    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * 自旋锁
     */
    public class SpinlockDemo {
    
        // int   0
        // Thread  null
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        // 加锁
        public void myLock(){
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "==> mylock");
    
            // 自旋锁
            while (!atomicReference.compareAndSet(null,thread)){
    
            }
        }
    
        // 解锁
        public void myUnLock(){
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "==> myUnlock");
            atomicReference.compareAndSet(thread,null);
        }
    }
    
    

    4.测试

    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestSpinLock {
        public static void main(String[] args) throws InterruptedException {
    //        ReentrantLock reentrantLock = new ReentrantLock();
    //        reentrantLock.lock();
    //        reentrantLock.unlock();
    
            // 底层使用的自旋锁CAS
            SpinlockDemo lock = new SpinlockDemo();
    
            new Thread(()-> {
                lock.myLock();
    
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
    
            },"T1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(()-> {
                lock.myLock();
    
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
    
            },"T2").start();
    
        }
    }
    
    

    在这里插入图片描述

    5.结果分析

    T1线程先拿到锁,随后T2线程也拿到锁,此时T1先执行,在T1释放锁之前T2都要等待T1释放锁才能执行下一步操作

    展开全文
  • 该库旨在在Erlang / Elixir中提供简单的锁定机制,类似于自旋锁在其他语言中的工作方式-除了使用消息来传达锁定信息外。 这对于需要锁定同步的库很有用,而不必滚动自己的库(但是很简单)。 可以通过任意数量的...
  • 自旋锁

    2019-10-15 11:52:06
    1.自旋锁定义: 实际就是while/do...while+CAS 尝试获取锁的线程不会立即阻塞,而是采用循环的方法尝试获取锁 这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU 2.代码描述: public class ...

    1.自旋锁定义: 实际就是while/do...while+CAS

    • 尝试获取锁的线程不会立即阻塞,而是采用循环的方法尝试获取锁
    • 这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

    2.代码描述:

    public class SpinLockDemo {
        AtomicReference<Thread> atomicReference=new AtomicReference<>();
        public static void main(String[] args) {
            SpinLockDemo spinLockDemo=new SpinLockDemo();
            //AA线程
            new Thread(()->{
                spinLockDemo.myLock();
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                spinLockDemo.myUnLock();
            },"AA").start();
    
            //BB线程 自旋等待AA使用完成
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{
                spinLockDemo.myLock();
                spinLockDemo.myUnLock();
            },"BB").start();
        }
        public void myLock(){
            Thread thread=Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"\t come in ####");
            while (!atomicReference.compareAndSet(null,thread)){
            }
        }
        public void myUnLock(){
            Thread thread=Thread.currentThread();
            atomicReference.compareAndSet(thread,null);
            System.out.println(Thread.currentThread().getName()+"\t invoke myUnLock()");
        }
    }
    运行结果:
    AA	 come in ####
    BB	 come in ####
    AA	 invoke myUnLock()
    BB	 invoke myUnLock()

    描述:

    • 线程AA首先进入MyLock()方法,输出 AA     come in ####
    • compareAndSet比较内存值是否为空,true,将内存值改为AA线程,A睡眠5秒线程
    • 线程BB进入MyLock()方法,输出 BB     come in ####
    • 此时compareAndSet,内存值为AA线程,while语句返回false,BB线程一直在此处判断,直到AA线程进入myUnLock()方法,将内存值重新改为null,输出 AA     invoke myUnLock(),AA线程运行结束
    • 线程BB进入myUnLock()方法,输出 BB     invoke myUnLock(),BB线程运行结束

     

    展开全文
  • 在多核操作系统环境下,同一时刻多任务同时访问内核,自旋锁可以很好地处理不同处理器之间存在的同步与互斥问题,但自旋锁如果使用不当,极易产生死锁,造成应用层功能无法实现,所以很有必要对自旋锁展开重点测试。...
  • 自旋锁 Go的实现。 它与共享相同的接口,旨在用于同步异常短暂的操作。 安装 go get -u github.com/tidwall/spinlock 接触 乔希·贝克 执照 spinlock源代码在MIT下可用。
  • 自旋锁和互斥锁区别

    2020-07-18 05:33:26
    线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的机制(lock)来对多个线程之间共 享的临界区(Critical Section)进行保护(另一种常用的同步机制是barrier)。
  • 自旋锁死锁

    千次阅读 2020-06-30 20:24:55
    自旋锁死锁 自旋锁内调用kmalloc或者copy_to_user之类的接口可能造成死锁。 这类函数的实现内有睡眠操作,睡眠时产生了进程调度,新的进程内如果也使用了该自旋锁,就会导致死锁。 这类问题非常普通,但很容易...

    自旋锁死锁

    自旋锁内调用kmalloc或者copy_to_user之类的接口可能造成死锁。

    这类函数的实现内有睡眠操作,睡眠时产生了进程调度,新的进程内如果也使用了该自旋锁,就会导致死锁。

     

    这类问题非常普通,但很容易忽略;

    屏蔽的方式:

    1,使用get_free_page申请内存

    2,对资源使用引用计数保护

    3,使用互斥锁

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

    阅读目录

     

    自旋锁(Spin lock)

    自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
        1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
        2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

    因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

     

     两种锁的加锁原理

    互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

    自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

     

    互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

     

    两种锁的区别

    互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

     

    两种锁的应用

    互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

    1 临界区有IO操作

    2 临界区代码复杂或者循环量大

    3 临界区竞争非常激烈

    4 单核处理器

    至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

    以上 本文来源:http://blog.csdn.net/susidian/article/details/51068858

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

       spin_lock

       pthread_spin_lock

        自旋锁(spinlock)是一种常用的互斥同步原语,当试图进入临界区的线程使用忙等待的方式检测锁的状态,若锁未被持有则尝试获取。这种忙等待的做法无谓的消耗了处理器资源,故只适合用于临界区非常小的代码片段,如linux下的中断处理函数。

        posix 已经实现了自旋锁相关的API,其主要包括 pthread_spin_lock,pthread_spin_trylock,pthread_spin_unlock。从实现的原理上来说,它属于busy-waiting类型的锁。假设一双核的机器上有两个线程(线程a,b),他们分别运行在core0,core1上,当线程a使用pthread_spin_lock去请求锁,那么线程a就会在core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。与mutex锁不同的是,线程a通过pthread_mutex_lock操作区得到一个临界区的时候,如果此时临界区被线程b所持有,那么线程a就会阻塞,core0 会进行上下文切换将线程a置于等待队列中,此时core0就可以运行其他的任务。可见spin_lock的好处是省去用户态切换与内核态的切换。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

    最近在内核频繁使用了自旋锁,自旋锁如果使用不当,极易引起死锁,在此总结一下。

    自旋锁是一个互斥设备,它只有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的某个位。希望获得某个特定锁的代码测试相关的位。如果锁可用,则“锁定”被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环(而不是休眠,这也是自旋锁和一般锁的区别)并重复检查这个锁,直到该锁可用为止,这就是自旋的过程。“测试并设置位”的操作必须是原子的,这样,即使多个线程在给定时间自旋,也只有一个线程可获得该锁。

    自旋锁最初是为了在多处理器系统(SMP)使用而设计的,但是只要考虑到并发问题,单处理器在运行可抢占内核时其行为就类似于SMP。因此,自旋锁对于SMP和单处理器可抢占内核都适用。可以想象,当一个处理器处于自旋状态时,它做不了任何有用的工作,因此自旋锁对于单处理器不可抢占内核没有意义,实际上,非抢占式的单处理器系统上自旋锁被实现为空操作,不做任何事情。

    自旋锁有几个重要的特性:(HY:不能休眠、中断、抢占,还要短,可以把自旋锁之间的代码想象成一个大的原子操作

    1、被自旋锁保护的临界区代码执行时不能进入休眠

    2、被自旋锁保护的临界区代码执行时是不能被被其他中断中断。

    3、被自旋锁保护的临界区代码执行时,内核不能被抢占。

    从这几个特性可以归纳出一个共性:被自旋锁保护的临界区代码执行时,它不能因为任何原因放弃处理器。

    考虑上面第一种情况,想象你的内核代码请求到一个自旋锁并且在它的临界区里做它的事情,在中间某处,你的代码失去了处理器。或许它已调用了一个函数(copy_from_user,假设)使进程进入睡眠。也或许,内核抢占发威(HY:抢占时机是?),一个更高优先级的进程将你的代码推到了一边。此时,正好某个别的线程想获取同一个锁,如果这个线程运行在和你的内核代码不同的处理器上(幸运的情况),那么它可能要自旋等待一段时间(可能很长),当你的代码从休眠中唤醒或者重新得到处理器并释放锁,它就能得到锁。而最坏的情况是,那个想获取锁得线程刚好和你的代码运行在同一个处理器上,这时它将一直持有CPU进行自旋操作,而你的代码是永远不可能有任何机会来获得CPU释放这个锁了,这就是悲催的死锁。

    考虑上面第二种情况,和上面第一种情况类似。假设我们的驱动程序正在运行,并且已经获取了一个自旋锁,这个锁控制着对设备的访问。在拥有这个锁得时候,设备产生了一个中断,它导致中断处理例程被调用,而中断处理例程在访问设备之前,也要获得这个锁。当中断处理例程和我们的驱动程序代码在同一个处理器上运行时,由于中断处理例程持有CPU不断自旋,我们的代码将得不到机会释放锁,这也将导致死锁。

    因此,如果我们有一个自旋锁,它可以被运行在(硬件或软件)中断上下文中的代码获得,则必须使用某个禁用中断的spin_lock形式的锁来禁用本地中断(注意,只是禁用本地CPU的中断,不能禁用别的处理器的中断),使用其他的锁定函数迟早会导致系统死锁(导致死锁的时间可能不定,但是发生上述死锁情况的概率肯定是有的,看处理器怎么调度了)。如果我们不会在硬中断处理例程中访问自旋锁,但可能在软中断(例如,以tasklet的形式运行的代码)中访问,则应该使用spin_lock_bh,以便在安全避免死锁的同时还能服务硬件中断。

    补充:

    锁定一个自旋锁的函数有四个:

    void spin_lock(spinlock_t *lock);      

    最基本得自旋锁函数,它不失效本地中断。

    void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

    在获得自旋锁之前禁用硬中断(只在本地处理器上),而先前的中断状态保存在flags中

    void spin_lockirq(spinlock_t *lock);

    在获得自旋锁之前禁用硬中断(只在本地处理器上),不保存中断状态

    void spin_lock_bh(spinlock_t *lock);

    在获得锁前禁用软中断,保持硬中断打开状态

    原文链接:https://blog.csdn.net/vividonly/article/details/6594195

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    单处理器环境的自旋锁

    单处理器环境不需要自旋锁,调用了自旋锁的函数,里面也不是自旋锁的实现,只不过是用了层壳而已

    原因是:

    如果是非抢占的系统:(高优先级的进程不能中止正在内核中运行的低优先级的进程而抢占CPU运行。进程一旦处于核心态(例如用户进程执行系统调用),则除非进程自愿放弃CPU,否则该进程将一直运行下去,直至完成或退出内核。)

    一个进程(进入到了内核态),或者线程用了自旋锁,没有执行完毕之前,没有别的进程或者内核线程跟他抢,即使是时间片到了之 后,接着还是分给本进程或者线程。

    如果是软中断或者是中断打断了怎么办? 没关系,关中断即可。也就是说,这时自旋锁退化成关开中断

     

    如果是抢占的系统:(即当进程位于内核空间时,有一个更高优先级的任务出现时,如果当前内核允许抢占,则可以将当前任务挂起,执行优先级更高的进程。)

    自旋锁的定义变成非抢占即可,这时高优先级进程就没法跟他抢了。中断是一样的处理。

     

    总之,单处理器来说,非抢占的话,自旋锁退化为 关开中断;

    对于抢占来说,自旋锁变成  禁止/打开抢占+关开中断

    展开全文
  • 什么是自旋锁 多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题。 问题引入: 获取到资源的线程A对这个资源加锁,其他线程比如B要访问这个资源首先要获得锁,...
  • 乐观锁 悲观锁 自旋锁 https://blog.csdn.net/wolfGuiDao/article/details/105253815
  • 2、自旋锁与适应性自旋锁 分类定义:锁住同步资源失败,要不要进行阻塞 1、概念 自旋锁的概念。阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于...
  • 自旋锁不是一种锁(类型),自旋锁是线程没有获取到锁时的一种等待策略。 自旋锁的提出背景 由于在多处理器环境中某些资源的有限性,有时需要互斥访问(mutual exclusion),这时候就需要引入锁的概念,只有获取了...
  • 自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去,在那里看是否该自旋锁的保持者已经释放了锁,\"自旋\"一词就是因此而得名。由于自旋锁使用...
  • java 自旋锁

    千次阅读 2019-04-26 15:05:18
    2 自旋锁原理 3 自旋锁时间阈值 4 自旋锁分类 5 自旋锁应用 6 自旋锁开启 二 自旋锁定义 何谓自旋锁? 它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源...
  • 什么是自旋锁 多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题。 获取到资源的线程A对这个资源加锁,其他线程比如B要访问这个资源首先要获得锁,而此时A持有这个...
  • 自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,517
精华内容 27,406
关键字:

自旋锁