CLH隊列鎖

NUMA與SMP

SMP(Symmetric Multi-Processor),即對稱多處理器結構,指服務器中多個CPU對稱工作,每個CPU訪問內存地址所需時間相同。其主要特徵是共享,包含對CPU,內存,I/O等進行共享。SMP的優點是能夠保證內存一致性,缺點是這些共享的資源很可能成爲性能瓶頸,隨着CPU數量的增加,每個CPU都要訪問相同的內存資源,可能導致內存訪問衝突,可能會導致CPU資源的浪費。常用的PC機就屬於這種。
NUMA(Non-Uniform Memory Access)非一致存儲訪問,將CPU分爲CPU模塊,每個CPU模塊由多個CPU組成,並且具有獨立的本地內存、I/O槽口等,模塊之間可以通過互聯模塊相互訪問,訪問本地內存的速度將遠遠高於訪問遠地內存(系統內其它節點的內存)的速度,這也是非一致存儲訪問NUMA的由來。NUMA優點是可以較好地解決原來SMP系統的擴展問題,缺點是由於訪問遠地內存的延時遠遠超過本地內存,因此當CPU數量增加時,系統性能無法線性增加。

CLH算法實現

CLH隊列中的結點QNode中含有一個locked字段,該字段若爲true表示該線程需要獲取鎖,且不釋放鎖,爲false表示線程釋放了鎖。結點之間是通過隱形的鏈表相連,之所以叫隱形的鏈表是因爲這些結點之間沒有明顯的next指針,而是通過myPred所指向的結點的變化情況來影響myNode的行爲。CLHLock上還有一個尾指針,始終指向隊列的最後一個結點。CLHLock的類圖如下所示:


當一個線程需要獲取鎖時,會創建一個新的QNode,將其中的locked設置爲true表示需要獲取鎖,然後線程對tail域調用getAndSet方法,使自己成爲隊列的尾部,同時獲取一個指向其前趨的引用myPred,然後該線程就在前趨結點的locked字段上旋轉,直到前趨結點釋放鎖。當一個線程需要釋放鎖時,將當前結點的locked域設置爲false,同時回收前趨結點。如下圖所示,線程A需要獲取鎖,其myNode域爲true,些時tail指向線程A的結點,然後線程B也加入到線程A後面,tail指向線程B的結點。然後線程A和B都在它的myPred域上旋轉,一量它的myPred結點的locked字段變爲false,它就可以獲取鎖掃行。明顯線程A的myPred locked域爲false,此時線程A獲取到了鎖。

整個CLH的代碼如下,其中用到了ThreadLocal類,將QNode綁定到每一個線程上,同時用到了AtomicReference,對尾指針的修改正是調用它的getAndSet()操作來實現的,它能夠保證以原子方式更新對象引用。
[java] view plain copy
 print?
  1. public class CLHLock implements Lock {  
  2.     AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());  
  3.     ThreadLocal<QNode> myPred;  
  4.     ThreadLocal<QNode> myNode;  
  5.   
  6.     public CLHLock() {  
  7.         tail = new AtomicReference<QNode>(new QNode());  
  8.         myNode = new ThreadLocal<QNode>() {  
  9.             protected QNode initialValue() {  
  10.                 return new QNode();  
  11.             }  
  12.         };  
  13.         myPred = new ThreadLocal<QNode>() {  
  14.             protected QNode initialValue() {  
  15.                 return null;  
  16.             }  
  17.         };  
  18.     }  
  19.   
  20.     @Override  
  21.     public void lock() {  
  22.         QNode qnode = myNode.get();  
  23.         qnode.locked = true;  
  24.         QNode pred = tail.getAndSet(qnode);  
  25.         myPred.set(pred);  
  26.         while (pred.locked) {  
  27.         }  
  28.     }  
  29.   
  30.     @Override  
  31.     public void unlock() {  
  32.         QNode qnode = myNode.get();  
  33.         qnode.locked = false;  
  34.         myNode.set(myPred.get());  
  35.     }  
  36. }     
從代碼中可以看出lock方法中有一個while循環,這 是在等待前趨結點的locked域變爲false,這是一個自旋等待的過程。unlock方法很簡單,只需要將自己的locked域設置爲false即可。

CLH優缺點

CLH隊列鎖的優點是空間複雜度低(如果有n個線程,L個鎖,每個線程每次只獲取一個鎖,那麼需要的存儲空間是O(L+n),n個線程有n個myNode,L個鎖有L個tail),CLH的一種變體被應用在了JAVA併發框架中。唯一的缺點是在NUMA系統結構下性能很差,在這種系統結構下,每個線程有自己的內存,如果前趨結點的內存位置比較遠,自旋判斷前趨結點的locked域,性能將大打折扣,但是在SMP系統結構下該法還是非常有效的。一種解決NUMA系統結構的思路是MCS隊列鎖。
發佈了29 篇原創文章 · 獲贊 19 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章