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()操作來實現的,它能夠保證以原子方式更新對象引用。
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;}};}@Overridepublic void lock() {QNode qnode = myNode.get();qnode.locked = true;QNode pred = tail.getAndSet(qnode);myPred.set(pred);while (pred.locked) {}}@Overridepublic void unlock() {QNode qnode = myNode.get();qnode.locked = false;myNode.set(myPred.get());}}
從代碼中可以看出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隊列鎖。
參考資料: A Hierarchical CLH Queue Lock
線程鎖系列(1):CLH Lock
The Art of Multiprocessor Programming