CLH鎖 、MCS鎖


一。引文

1.1 SMP(Symmetric Multi-Processor)

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

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

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

1.2 NUMA(Non-Uniform Memory Access)

非一致存儲訪問,將CPU分為CPU模塊,每個CPU模塊由多個CPU組成,並且具有獨立的本地內存、I/O槽口等,模塊之間可以通過互聯模塊相互訪問,

訪問本地內存的速度將遠遠高於訪問遠地內存(系統內其它節點的內存)的速度,這也是非一致存儲訪問的由來。NUMA較好地解決SMP的擴展問題,

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

 

二。CLH

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

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

 

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

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

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

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

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

  如下圖,線程A需要獲取鎖,其myNode域為true,tail指向線程A的結點,然后線程B也加入到線程A后面,tail指向線程B的結點。然后線程A和B

都在其myPred域上旋轉,一旦它的myPred結點的locked字段變為false,它就可以獲取鎖。明顯線程A的myPred locked域為false,此時線程A獲取

到了鎖。

三。CLH代碼示例

public class CLHLock implements Lock {  
    AtomicReference<QNode> tail ;  
    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());  
    }  
}

 

 

四。CLH分析

CLH隊列鎖的優點是空間復雜度低(如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那么需要的存儲空間是O(L+n),n個線程有n個

myNode,L個鎖有L個tail),CLH的一種變體被應用在了JAVA並發框架中(AbstractQueuedSynchronizer.Node)。CLH在SMP系統結構下

該法是非常有效的。但在NUMA系統結構下,每個線程有自己的內存,如果前趨結點的內存位置比較遠,自旋判斷前趨結點的locked域,性能

將大打折扣,一種解決NUMA系統結構的思路是MCS隊列鎖。

 

五。MCS

MSC與CLH最大的不同並不是鏈表是顯示還是隱式,而是線程自旋的規則不同:CLH是在前趨結點的locked域上自旋等待,而MCS是在自己的

結點的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獲取鎖

 

六。代碼實現

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

    @Override
    public void lock() {
        tail = new AtomicReference<QNode>(new QNode());  
        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