乐趣区

关于aqs:AQS源码解读番外篇四种自旋锁原理详解Java代码实现SpinLockTicketSpinLockCLHMCS

何为自旋锁

自旋锁是为实现爱护共享资源而提出的一种锁机制。自旋锁与 Java 中的 synchronizedLock不同,不会引起调用线程阻塞睡眠。如果有线程持有自旋锁,调用线程就会始终循环检测锁的状态,直到其余线程开释锁,调用线程才进行自旋,获取锁。

自旋锁的劣势和缺点

自旋锁的长处很显著:自旋锁不会使线程状态进行切换,始终处于用户态,即不会频繁产生上下文切换,执行速度快,性能高。

正是因为其不进行上下文切换的长处,也使得某些状况下,缺点也很显著:如果某个线程持有锁的工夫过长,或者线程间竞争强烈,就会导致某些期待获取锁的线程进入长时间循环期待,耗费 CPU,从而造成 CPU 占用率极高。

自旋锁的实用场景

自旋锁实用于被锁代码块执行工夫很短,即加锁工夫很短的场景。

常见自旋锁实现

比拟有名的四种自旋锁:传统自旋锁 SpinLock,排队自旋锁TicketSpinLockCLH 自旋锁,MCS自旋锁。这四种自旋锁的基本原理都是在 CAS 的根底上实现的,各有各的特点,且逐渐优化。

SpinLock 传统自旋锁的劣势和有余

实现原理

SpinLock原理很简略,多个线程循环 CAS 批改一个共享变量,批改胜利则进行自旋获取锁。

代码实现

实现接口参考了 JavaLock,外围办法在 tryAcquiretryRelease。获取锁的形式实现了自旋,可中断自旋,自旋超时中断,不自旋。共享变量 state 不仅作为锁的状态标记(state=0锁闲暇,state>0有线程持有锁),同时可作为自旋锁重入的次数。exclusiveOwnerThread记录以后持有锁的线程。

public class SpinLock implements Lock {
    protected volatile int state = 0;
    private volatile Thread exclusiveOwnerThread;
    @Override
    public void lock() {for(;;) {
            // 直到获取锁胜利,才完结循环
            if (tryAcquire(1)) {return;}
        }
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {for(;;) {if (Thread.interrupted()) {
                // 有被中断  抛异样
                throw new InterruptedException();}
            if (tryAcquire(1)) {return;}
        }
    }

    /**
     * 返回获取锁的后果,不会自旋
     * @return
     */
    @Override
    public boolean tryLock() {return tryAcquire(1);
    }

    /**
     * 返回获取自旋锁的后果,会自旋一段时间,超时后进行自旋
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {long nanosTimeout = unit.toNanos(time);
        if (nanosTimeout <= 0L) {return false;}
        final long deadline = System.nanoTime() + nanosTimeout;
        for(;;) {if (Thread.interrupted()) {
                // 有被中断  抛异样
                throw new InterruptedException();}
            if (tryAcquire(1)) {return true;}
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L) {
                // 超时自旋,间接返回 false
                return false;
            }
        }
    }

    @Override
    public void unlock() {tryRelease(1);
    }

    @Override
    public Condition newCondition() {throw new UnsupportedOperationException();
    }

    public int getState() {return state;}

    /**
     * 获取持有锁的以后线程
     * @return
     */
    public Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}

    /**
     * 获取以后线程重入次数
     * @return
     */
    public int getHoldCount() {return isHeldExclusively() ? getState() : 0;}

    /**
     * 开释锁
     * @param releases
     * @return
     */
    protected boolean tryRelease(int releases) {int c = getState() - releases;
        Thread current = Thread.currentThread();
        if (current != getExclusiveOwnerThread())
            // 不是以后线程,不能 unLock 抛异样
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c <= 0) {
            // 每次减一,c = 0, 证实没有线程持有锁了,能够开释了
            free = true;
            c = 0;
            setExclusiveOwnerThread(null);
            System.out.println(String.format("spin un lock ok, thread=%s;", current.getName()));
        }
        // 排它锁,只有以后线程才会走到这,是线程平安的  批改 state
        setState(c);
        return free;
    }
    /**
     * 获取锁
     * @param acquires
     * @return
     */
    protected boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 若此时锁空着,则再次尝试抢锁
            if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);
                System.out.println(String.format("spin lock ok, thread=%s;", current.getName()));
                return true;
            }
        }
        // 若以后持锁线程是以后线程(重入性)
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 重入
            setState(nextc);
            System.out.println(String.format("spin re lock ok, thread=%s;state=%d;", current.getName(), getState()));
            return true;
        }
        return false;
    }
    /**
     * 判断以后线程是否持有锁
     * @return
     */
    protected final boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();}

    protected void setState(int state) {this.state = state;}

    protected void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {this.exclusiveOwnerThread = exclusiveOwnerThread;}

    protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    protected static final Unsafe getUnsafe() {
        try {
            // 不能够间接应用 Unsafe,须要通过反射,当然也能够间接应用 atomic 类
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            Unsafe unsafe = (Unsafe) theUnsafe.get(null);
            if (unsafe != null) {return unsafe;}
        } catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();
        }
        throw new RuntimeException("get unsafe is null");
    }
    private static final Unsafe unsafe = getUnsafe();
    private static final long stateOffset;
    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                    (SpinLock.class.getDeclaredField("state"));
        } catch (Exception ex) {throw new Error(ex); }
    }

SpinLock 的特点

  • 劣势:SpinLock实现原理简略,线程间没有频繁的上下文切换,执行速度快,性能高。
  • 缺点:SpinLock是不偏心的,无奈满足等待时间最长的线程优先获取锁,造成“线程饥饿”。
  • 缺点:因为每个申请自旋锁的处理器均在一个全局变量上自旋检测,系统总线将因为处理器间的缓存同步而导致沉重的流量,从而升高了零碎整体的性能。

因为传统自旋锁无序竞争的实质特点,内核执行线程无奈保障何时能够取到锁,某些执行线程可能须要期待很长时间,导致“不偏心”问题的产生。有两个方面的起因:

  1. 随着处理器个数的一直减少,自旋锁的竞争也在加剧,天然导致更长的等待时间。
  2. 开释自旋锁时的重置操作将使所有其它正在自旋期待的处理器的缓存有效化,那么在处理器拓扑构造中邻近自旋锁拥有者的处理器可能会更快地刷新缓存,因此增大取得自旋锁的机率。

TicketSpinLock 排队自旋锁的优化与有余

因为 SpinLock 传统自旋锁是不偏心的,且在锁竞争强烈的服务器,”不偏心“问题尤为重大。因而偏心的排队自旋锁就应运而生了。(Linux 内核开发者 Nick Piggin 在 Linux 内核 2.6.25 版本中引入了排队自旋锁,并不是他创造的排队自旋锁,排队自旋锁只是一种思维,Windows 中排队自旋锁采取了不一样的实现逻辑。)

实现原理

排队自旋锁通过保留执行线程申请锁的程序信息来解决“不偏心”问题。TicketSpinLock依然应用原有 SpinLock 的数据结构,为了保留程序信息,退出了两个新变量,别离是锁须要服务的序号 (serviceNum) 和将来锁申请者的票据序号 (ticketNum)。当serviceNum=ticketNum 时,代表锁闲暇,线程能够获取锁。

代码实现

根本共享变量serviceNumticketNum,辅助变量threadOwnerTicketNumstateexclusiveOwnerThread

线程获取锁,获取排队序号ticketNum,并自增排队序号,自旋比拟获取的排队序号和以后服务序号是否相等(serviceNum != myTicketNum),相等则进行自旋,获取锁。

threadOwnerTicketNum变量不是必须的,然而如果要实现重入锁,是必不可少的,用于记录每个线程持有的排队序号。当检测线程持有的排队序号为空时,可获取排队序号,如果不为空,则此时有其余线程持有锁。判断持有锁的线程是否为以后线程,是则重入。

stateexclusiveOwnerThread 用于重入锁的实现,然而并不能代表锁的持有状态(可能有瞬时性)。

线程开释锁,因为是重入锁,须要 state 自减为 0 时,serviceNum才自减少 1。

因为 serviceNumstateexclusiveOwnerThread 的操作环境是天生线性平安的,所以不须要CAS

public class TicketSpinLock {
    // 服务序号,不须要 cas,因为开释锁的只有一个线程,serviceNum++ 的环境是天生平安的
    private volatile int serviceNum = 0;
    // 排队序号,cas
    private AtomicInteger ticketNum = new AtomicInteger(0);
    // 记录以后线程的排队号,次要的作用是为了实现可重入,避免屡次取号
    private ThreadLocal<Integer> threadOwnerTicketNum = new ThreadLocal<Integer>();
    //state 不作为锁状态标记,只代表锁重入的次数
    protected volatile int state = 0;
    private volatile Thread exclusiveOwnerThread;
    public void lock() {final Thread current = Thread.currentThread();
        Integer myTicketNum = threadOwnerTicketNum.get();
        if (myTicketNum == null) {myTicketNum = ticketNum.getAndIncrement();
            threadOwnerTicketNum.set(myTicketNum);
            while (serviceNum != myTicketNum) {}
            // 若拿的排队号刚好等于服务序号,阐明能够获取锁,即获取锁胜利
            setExclusiveOwnerThread(current);
            state ++ ;
            System.out.println(String.format("ticket lock ok, thread=%s;state=%d;serviceNum=%d;next-ticketNum=%d;", current.getName(), getState(), serviceNum, ticketNum.get()));
            return;
        }
        // 若曾经取号,判断以后持锁线程是以后线程(重入性)
        if (current == getExclusiveOwnerThread()) {
            // 重入
            state++;
            System.out.println(String.format("ticket re lock ok, thread=%s;state=%d;serviceNum=%d;next-ticketNum=%d;", current.getName(), getState(), serviceNum, ticketNum.get()));
            return;
        }
    }
    public void unlock() {if (Thread.currentThread() != getExclusiveOwnerThread())
            // 不是以后线程,不能 unLock 抛异样
            throw new IllegalMonitorStateException();
        state--;
        if (state == 0) {
            // 齐全开释锁,owner+1
            // 服务序号是线性平安的,无需 cas
            threadOwnerTicketNum.remove();
            setExclusiveOwnerThread(null);
            serviceNum ++;
            System.out.println(String.format("ticket un lock ok, thread=%s;next-serviceNum=%d;ticketNum=%d;", Thread.currentThread().getName(), serviceNum, ticketNum.get()));
        }
    }

    public int getState() {return state;}

    public Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {this.exclusiveOwnerThread = exclusiveOwnerThread;}
}

TicketSpinLock 的特点

TicketSpinLock是偏心锁,根本解决了传统自旋锁“不偏心”问题,然而并没有解决处理器缓存同步问题。

在大规模多处理器零碎和 NUMA 零碎中,排队自旋锁(包含传统自旋锁)同样存在一个比较严重的性能问题:因为执行线程均在同一个共享变量上自旋,将导致所有参加排队自旋锁操作的处理器的缓存变得有效。如果排队自旋锁竞争比拟强烈的话,频繁的缓存同步操作会导致系统总线和处理器内存的流量沉重,从而大大降低了零碎整体的性能。

CLH 队列自旋锁的优化与有余

CLH(Craig, Landin, and Hagersten)锁是基于链表实现的 FIFO 队列偏心锁。CLH 是其三个发明者的人名缩写(Craig, Landin, and Hagersten)。

实现原理

获取锁的线程先入队列,入到队列尾部后一直自旋查看前驱节点的状态,前驱为空 or 检测到前驱开释锁则该节点获取锁。入队列尾部是 CAS 操作,保障了有序出入队列。

节点获取锁的条件:前驱为空 or 检测到前驱开释锁

代码实现

CLH 锁只是一种思维,实现的形式很多,网上有基于隐式链表实现的,即节点与节点之间不是实在连贯,只是以后线程记录了前驱节点和本人的节点。

如下代码实现的链表是实在连贯的,即线程以后节点有前驱指针的变量(prev)。节点除了有前驱指针外还有一个 locked 变量记录节点锁持有状态,locked=true代表线程节点正在持有锁,或者须要锁,初始线程节点 locked 为 true,开释锁后将 locked 改为 false,以让其后继自旋感知前驱开释锁了,并进行自旋获取锁

public class CLHSpinLock {
    class Node {
        volatile Node prev;
        /**
         * true 示意正在持有锁,或者须要锁
         * false 示意开释锁
         */
        volatile boolean locked = true;
        volatile Thread thread;
        Node(Thread thread) {this.thread = thread;}

        boolean isPrevLocked() {return  prev != null && prev.locked;}
        String getPrevName() {return prev == null ? "null" : prev.thread.getName();
        }
    }

    private volatile AtomicReference<Node> tail = new AtomicReference<Node>();
    // 线程和 node key-value
    private ThreadLocal<Node> threadNode = new ThreadLocal<Node>();
    // 记录持有锁的以后线程
    private volatile Thread exclusiveOwnerThread;
    // 记录重入
    protected volatile int state = 0;
    // 因为 exclusiveOwnerThread 和 state 只是作为记录,线程获取锁后才会设置这两个值,具备有瞬时性,所以不能作为锁是否闲暇的判断标记

    public Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {this.exclusiveOwnerThread = exclusiveOwnerThread;}

    /**
         * cas 自旋入队列 -> 尾部
         * @return
         */
    Node enq() {Node node = new Node(Thread.currentThread());
        threadNode.set(node);
        for (;;) {Node prev = tail.get();
            //cas 设置 tail 指针指向 node
            if (tail.compareAndSet(prev, node)) {
                node.prev = prev;
                return node;
            }
        }
    }

    public void lock() {Node node = threadNode.get();
        if (node != null && getExclusiveOwnerThread() != null && node.thread == getExclusiveOwnerThread()) {
            /**
             * 个别状况 node != null,阐明有同一个线程曾经调用了 lock()
             * 判断持有锁的线程是 node.thread,重入
             */
            state++;
            System.out.println(String.format("re lock thread=%s;state=%d;", node.thread.getName(), state));
            return;
        }
        node = enq();
        while (node.isPrevLocked()) { }
        // 前驱未持有锁,阐明能够获取锁,即获取锁胜利, prev 设置为 null,断开与链表的连贯,相当于前驱出队列
        System.out.println(String.format("clh get lock ok, thread=%s;prev=%s;", node.thread.getName(), node.getPrevName()));
        setExclusiveOwnerThread(node.thread);
        state++;
        node.prev = null;
    }

    public void unlock() {Node node = threadNode.get();
        if (node.thread != getExclusiveOwnerThread()) {throw new IllegalMonitorStateException();
        }
        // 在 node.setLocked(false) 之前设置 state
        --state;
        // 齐全开释锁,locked 改为 false,让其后继感知前驱锁开释并进行自旋
        if (state == 0) {System.out.println(String.format("clh un lock ok, thread=%s;", node.thread.getName()));
            setExclusiveOwnerThread(null);
            node.locked = false;
            threadNode.remove();
            node = null; //help gc
   }
}

CLH 锁的特点

CLH 锁是偏心的,且空间复杂度是常数级。在肯定水平上加重了排队自旋锁和传统自旋锁在同一个共享变量上自旋的问题,然而并不彻底。

CLH 在 SMP 系统结构(每个 cpu 缓存统一)下是十分无效的。但在 NUMA 系统结构(每个 cpu 有各自的缓存)下,每个线程有本人的内存,如果前驱节点的内存地位比拟远,自旋判断前驱节点的 locked 状态,性能将大打折扣。

MCS 锁对 CLH 锁的优化

因为 CLH 前驱节点的内存地位可能较远,在 NUMA 系统结构下导致自旋判断前驱节点的 locked 状态的性能很低,所以一种解决 NUMA 系统结构的思路就是 MCS 队列锁。MCS 也是其发明者人名缩写(John M. Mellor-Crummey 和 Michael L. Scott)。

实现原理

MCS 和 CLH 区别在于,MCS 是对本人节点的锁状态一直自旋 当前驱为空即队列中只有本人一个节点或者检测到本人节点锁状态能够获取锁,则线程获取锁。

代码实现

MCS 入队列形式,代码中实现了两种,一种是节点有前驱指针(enq()),这样 MCS 中的链表队列就是双向队列,另一种是入队列后返回前驱节点(enq1()),这样节点的前驱指针就是隐式的。

外部类 Node 中 prev 属性可有可无,next必须,节点开释锁时须要被动告诉后继。locked代表锁持有状态,locked=false 代表线程未持有锁,locked=true代表线程可持有锁,初始节点locked=false

获取锁,线程先入队列尾部,并查看前驱是否为空,为空则进行自旋获取锁,不为空判断以后节点的 locked 是否为 true,为 true 进行自旋获取锁。

开释锁,以后节点将后继节点的 locked 改为 true,以让后继感知到本人的锁状态是能够获取锁了。如果以后节点后继为空,则自旋清空队列。

public class MCSSpinLock {
    class Node {
        //prev 可有可无
        volatile Node prev;
        volatile Node next;
        //false 代表未持有锁,true 代表可持有锁
        volatile boolean locked = false;
        volatile Thread thread;
        Node(Thread thread) {this.thread = thread;}

        public boolean shouldLocked() {return prev == null || locked;}

        public String getNextName() {return next == null ? "null" : next.thread.getName();
        }
    }
    private volatile AtomicReference<Node> tail = new AtomicReference<Node>();
    // 线程和 node key-value
    private ThreadLocal<Node> threadNode = new ThreadLocal<Node>();
    // 记录持有锁的以后线程
    private volatile Thread exclusiveOwnerThread;
    // 记录重入
    protected volatile int state = 0;
    // 因为 exclusiveOwnerThread 和 state 只是作为记录,线程获取锁后才会设置这两个值,具备有瞬时性,所以不能作为锁是否闲暇的判断标记


    public Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}

    public void setExclusiveOwnerThread(Thread exclusiveOwnerThread) {this.exclusiveOwnerThread = exclusiveOwnerThread;}
    /**
     * cas 自旋入队列 -> 尾部
     * @return
     */
    Node enq() {Node node = new Node(Thread.currentThread());
        threadNode.set(node);
        for (;;) {Node t = tail.get();
            if (tail.compareAndSet(t, node)) {if (t != null) {
                    t.next = node;
                    node.prev = t;
                }
                return node;
            }
        }
    }

    /**
     *
     * @return 返回前驱
     */
    Node enq1() {Node node = new Node(Thread.currentThread());
        threadNode.set(node);
        Node prev = tail.getAndSet(node);
        if (prev != null) {prev.next = node;}
        return prev;
    }

    public void lock() {Node node = threadNode.get();
        if (node != null && getExclusiveOwnerThread() != null && node.thread == getExclusiveOwnerThread()) {
            /**
             * 个别状况 node != null,阐明有同一个线程曾经调用了 lock()
             * 持有锁的线程是 node.thread,重入
             */
            state++;
            System.out.println("re lock thread=" + node.thread.getId() + "state=" + state);

            return;
        }
        node = enq();
        while (!node.shouldLocked()) {}
        // 判断 node 是否应该获取锁,若 prev == null or node.locked=true,代表应该获取锁。则完结自旋
        if (!node.locked) {
            // 当前驱为空时的状况,不过不改也问题不大
            node.locked = true;
        }
        state++;
        setExclusiveOwnerThread(node.thread);
        System.out.println(String.format("mcs get lock ok, thread=%s;locked=%b;node==tail=%b;next=%s;", node.thread.getName(), node.locked, node == tail.get(), node.getNextName()));
    }

    public void lock1() {Node node = threadNode.get();
        if (node != null && getExclusiveOwnerThread() != null && node.thread == getExclusiveOwnerThread()) {
            /**
             * 个别状况 node != null,阐明有同一个线程曾经调用了 lock()
             * 持有锁的线程是 node.thread,重入
             */
            state++;
            System.out.println("re lock thread=" + node.thread.getId() + "state=" + state);

            return;
        }
        Node prev = enq1();
        node = threadNode.get();
        while (prev != null && !node.locked) {}
        // 判断 node 是否应该获取锁,若 prev == null or node.locked=true,代表应该获取锁。则完结自旋
        if (!node.locked) {
            // 当前驱为空时的状况,不过不改也问题不大
            node.locked = true;
        }
        state++;
        setExclusiveOwnerThread(node.thread);
        System.out.println(String.format("mcs get lock ok, thread=%s;locked=%b;node==tail=%b;next=%s;", node.thread.getName(), node.locked, node == tail.get(), node.getNextName()));
    }

    public void unlock() {Node node = threadNode.get();
        if (node.thread != getExclusiveOwnerThread()) {throw new IllegalMonitorStateException();
        }
        // 在 node.setLocked(false) 之前设置 state
        state--;
        // 齐全开释锁,将前驱 locked 改为 false,让其后继感知锁闲暇并进行自旋
        if (state != 0) {return;}
        // 后继为空,则清空队列,将 tail  cas 为 null,// 如果此时刚好有节点入队列则退出循环,持续被动告诉后继
        while (node.next == null) {if (tail.compareAndSet(node, null)) {
                // 设置 tail 为 null,threadNode remove
                threadNode.remove();
                System.out.println(String.format("mcs un lock ok, thread=%s;clear queue", node.thread.getName()));
                return;
            }
        }
        //threadNode 后继不为空 设置后继的 locked=true,被动告诉后继获取锁
        System.out.println(String.format("mcs un lock ok, thread=%s;next-thread=%s;", node.thread.getName(), node.getNextName()));
        // 在 node.next.locked 前,设置 setExclusiveOwnerThread 为 null
        setExclusiveOwnerThread(null);
        node.next.locked = true;
        threadNode.remove();
        node = null; //help gc
    }
}

MCS 锁的特点

MCS 锁是偏心的,且空间复杂度是常数级。彻底解决了 CLH 锁、排队自旋锁和传统自旋锁在同一个共享变量上自旋的问题,所以 MCS 锁在没有处理器缓存一致性协定保障的零碎中也能很好地工作。

SMP 和 NUMP 简介

SMP(Symmetric Multi-Processor)

对称多处理器构造,所有的 CPU 共享全副资源,如总线,内存和 I / O 零碎等。

  • 多个 CPU 之间没有区别,平等地拜访内存、外设等共享资源,每个 CPU 拜访内存中的任何地址所需工夫雷同。因而 SMP 也被称为统一存储器拜访构造 (UMA:Uniform Memory Access)。
  • 如果多个处理器同时申请拜访一个资源(例如同一段内存地址),由硬件、软件的锁机制去解决资源争用问题。
  • SMP 性能扩大无限,每一个共享的环节都可能造成 SMP 服务器扩大时的瓶颈,而最受限制的则是内存。因为每个 CPU 必须通过雷同的内存总线拜访雷同的内存资源,因而随着 CPU 数量的减少,内存拜访抵触将迅速减少,最终会造成 CPU 资源的节约,使 CPU 性能的有效性大大降低。

NUMA(Non-Uniform Memory Access)

非统一存储拜访构造,具备多个 CPU 模块,每个 CPU 模块由多个 CPU 组成,并且具备独立的本地内存、I/O 槽口等等,模块之间能够通过互联模块 (如称为 Crossbar Switch) 进行连贯和信息交互,因而每个 CPU 能够拜访整个零碎的内存。

显然,拜访本地内存的速度将远远高于拜访远地内存 (零碎内其它节点的内存) 的速度,这也是非统一存储拜访 NUMA 的由来。因为这个特点,为了更好地施展零碎性能,开发应用程序时须要尽量减少不同 CPU 模块之间的信息交互。

利用 NUMA 技术,能够较好地解决原来 SMP 零碎的扩大问题,在一个物理服务器内能够反对上百个 CPU。但 NUMA 技术同样有肯定缺点,因为拜访远地内存的延时远远超过本地内存,因而当 CPU 数量减少时,零碎性能无奈线性减少。

总结

  • 自旋锁,线程间不会进行上下文切换(即用户态和内核态间切换),执行速度快,性能高。
  • 自旋锁实用于线程竞争少,被锁代码执行工夫短的状况。
  • 传统自旋锁是非偏心锁,线程竞争强烈会导致“不偏心”问题减轻,呈现“线程饥饿”状况。
  • 排队自旋锁是偏心的,然而其和传统自旋锁都有一个致命的缺点,在 NUMP 系统结构下,多个线程自旋同一个全局变量,导致处理器缓存频繁生效刷新,竞争强烈的状况下系统总线和处理器流量加大,使得零碎整体性能升高。
  • CLH 是基于链表实现的 FIFO 队列偏心锁,其一直 自旋检测前驱节点的锁状态,以判断以后节点是否应该获取锁。在 NUMA 系统结构(每个 cpu 有各自的缓存)下,如果前驱节点的内存地位比拟远,自旋判断前驱节点的 locked 状态,性能将大打折扣。
  • MCS 也是基于链表实现的 FIFO 队列偏心锁,其一直 自旋检测本人节点的锁状态,判断是否应该获取锁。因为是自旋本人本地的节点的变量,所以不存在 CLH 因为前驱内存地位较远导致自旋性能折扣问题。

四种自旋锁实现代码都通过测试,如有问题请评论留言,我会及时改过。理论应用中不要打印日志,频繁的 io 操作会影响锁的性能。

PS: 如若文章中有谬误了解,欢送批评指正,同时十分期待你的评论、点赞和珍藏。我是徐同学,愿与你共同进步!

参考:

https://www.ibm.com/developerworks/cn/linux/l-cn-spinlock/

https://www.ibm.com/developerworks/cn/linux/l-cn-mcsspinlock/

https://www.cnblogs.com/yuyutianxia/p/4296220.html

https://blog.csdn.net/yhb1047818384/article/details/84677203

https://www.cnblogs.com/yubo/archive/2010/04/23/1718810.html

退出移动版