CLH鎖 、MCS鎖


一、

1、SMP(Symmetric Multi-Processor)

      SMP(Symmetric Multi-Processing)對稱多處理器結構,指服務器中多個CPU對稱工作,每個CPU訪問內存地址所需時間相同。其主要特征是共享,包含對CPU,內存,I/O等進行共享。

      SMP能夠保證內存一致性,但這些共享的資源很可能成為性能瓶頸,隨着CPU數量的增加,每個CPU都要訪問相同的內存資源,可能導致內存訪問沖突,

      可能會導致CPU資源的浪費。常用的PC機就屬於這種。

2、NUMA(Non-Uniform Memory Access)

      非一致存儲訪問,將CPU分為CPU模塊,每個CPU模塊由多個CPU組成,並且具有獨立的本地內存、I/O槽口等,模塊之間可以通過互聯模塊相互訪問,訪問本地內存的速度將遠遠高於訪問遠地內存(系統內其它節點的內存)的速度,這也是非一致存儲訪問的由來。NUMA較好地解決SMP的擴展問題,

      當CPU數量增加時,因為訪問遠地內存的延時遠遠超過本地內存,系統性能無法線性增加。

二、

1、CLH鎖

      CLH(Craig, Landin, and Hagersten  locks): 是一個自旋鎖,能確保無飢餓性,提供先來先服務的公平性。

      CLH鎖也是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅的狀態,如果發現前驅釋放了鎖就結束自旋。

      當一個線程需要獲取鎖時:

      a.創建一個的QNode,將其中的locked設置為true表示需要獲取鎖

      b.線程對tail域調用getAndSet方法,使自己成為隊列的尾部,同時獲取一個指向其前趨結點的引用myPred

      c.該線程就在前趨結點的locked字段上旋轉,直到前趨結點釋放鎖

      d.當一個線程需要釋放鎖時,將當前結點的locked域設置為false,同時回收前趨結點

  如下圖,線程A需要獲取鎖,其myNode域為true,tail指向線程A的結點,然后線程B也加入到線程A后面,tail指向線程B的結點。然后線程A和B都在其myPred域上旋轉,一旦它的myPred結點的locked字段變為false,它就可以獲取鎖。明顯線程A的myPred locked域為false,此時線程A獲取到了鎖。

191818267839952

 

2、CLH代碼示例

public class CLHLock implements Lock {  
    AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());  
    ThreadLocal<QNode> myPred;  
    ThreadLocal<QNode> myNode;  
  
    public CLHLock() {  
        tail = new AtomicReference<QNode>(new QNode());  
        myNode = new ThreadLocal<QNode>() {  
            protected QNode initialValue() {  
                return new QNode();  
            }  
        };  
        myPred = new ThreadLocal<QNode>() {  
            protected QNode initialValue() {  
                return null;  
            }  
        };  
    }  
  
    @Override  
    public void lock() {  
        QNode qnode = myNode.get();  
        qnode.locked = true;  
        QNode pred = tail.getAndSet(qnode);  
        myPred.set(pred);  
        while (pred.locked) {  
        }  
    }  
  
    @Override  
    public void unlock() {  
        QNode qnode = myNode.get();  
        qnode.locked = false;  
        myNode.set(myPred.get());  
    }  
}

 

3、CLH分析

      CLH隊列鎖的優點是空間復雜度低(如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那么需要的存儲空間是O(L+n),n個線程有n個。myNode,L個鎖有L個tail),CLH的一種變體被應用在了JAVA並發框架中。

      CLH在SMP系統結構下該法是非常有效的。但在NUMA系統結構下,每個線程有自己的內存,如果前趨結點的內存位置比較遠,自旋判斷前趨結點的locked域,性能將大打折扣,一種解決NUMA系統結構的思路是MCS隊列鎖。

三、

1、MCS鎖

      MSC與CLH最大的不同並不是鏈表是顯示還是隱式,而是線程自旋的規則不同:CLH是在前趨結點的locked域上自旋等待,而MSC是在自己的結點的locked域上自旋等待。正因為如此,它解決了CLH在NUMA系統架構中獲取locked域狀態內存過遠的問題。

      MCS隊列鎖的具體實現如下:

      a. 隊列初始化時沒有結點,tail=null

      b. 線程A想要獲取鎖,於是將自己置於隊尾,由於它是第一個結點,它的locked域為false

      c. 線程B和C相繼加入隊列,a->next=b,b->next=c。且B和C現在沒有獲取鎖,處於等待狀態,所以它們的locked域為true,尾指針指向線程C對應的結點

      d. 線程A釋放鎖后,順着它的next指針找到了線程B,並把B的locked域設置為false。這一動作會觸發線程B獲取鎖

123

 

2、代碼實現

public class MCSLock implements Lock {
    AtomicReference<QNode> tail;
    ThreadLocal<QNode> myNode;

    @Override
    public void lock() {
        QNode qnode = myNode.get();
        QNode pred = tail.getAndSet(qnode);
        if (pred != null) {
            qnode.locked = true;
            pred.next = qnode;

            // wait until predecessor gives up the lock
            while (qnode.locked) {
            }
        }
    }

    @Override
    public void unlock() {
        QNode qnode = myNode.get();
        if (qnode.next == null) {
            if (tail.compareAndSet(qnode, null))
                return;
            
            // wait until predecessor fills in its next field
            while (qnode.next == null) {
            }
        }
        qnode.next.locked = false;
        qnode.next = null;
    }

    class QNode {
        boolean locked = false;
        QNode next = null;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM