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;
-
}
-
};
-
}
-
-
@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());
-
}
-
}
從代碼中可以看出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隊列鎖。