CLH隊列鎖


http://blog.csdn.net/aesop_wubo/article/details/7533186


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

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


SMP(Symmetric Multi-Processor)。即對稱多處理器結構,指server中多個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
  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方法非常easy。僅僅須要將自己的locked域設置為false就可以。



CLH優缺點

CLH隊列鎖的長處是空間復雜度低( 假設有n個線程。L個鎖,每一個線程每次僅僅獲取一個鎖,那么須要的存儲空間是O(L+n),n個線程有n個myNode。L個鎖有L個tail),CLH的一種變體被應用在了JAVA並發框架中。唯一的缺點是在NUMA系統結構下性能非常差。在這樣的系統結構下,每一個線程有自己的內存,假設前趨結點的內存位置比較遠。自旋推斷前趨結點的locked域,性能將大打折扣,可是在SMP系統結構下該法還是非常有效的。

一種解決NUMA系統結構的思路是MCS隊列鎖。


參考資料:

The Art of Multiprocessor Programming


免責聲明!

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



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