天天看點

MCS鎖

1、 為什麼要引入MCS鎖?

         在NUMA架構體系下,通路remote memory的速度要遠遠慢于通路local memory的速度。如下圖所示(引自Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit):

MCS鎖

 在前一篇文章中分析了CLH算法,由于每個線程都在其前驅線程的QNode節點的locked域自旋,在NUMA體系下,即每個線程都在其前驅線程的remote memory位置自旋,是以性能上會打折扣。

那麼在NUMA體系下,如果每個線程自旋的位置都能固定在自己的local memory中,則性能相比于CLH算法,應該會有一定的提升。MCS鎖就是基于這種理念設計出來的。

2、MCS鎖介紹

       同CLH鎖一樣,每個申請鎖的線程通過一個QNode對象表示,QNode中有一個volatile boolean類型的成員變量locked,locked為true,則表示對應的線程已經擷取到了鎖或者正在等待擷取鎖;locked為false,則表示對應的線程已經釋放了鎖。

       鎖則由多個QNode節點組成連結清單标示,與CLH鎖不同的是,MCS中的鎖不是虛拟連結清單,而是實際連結清單,每個QNode節點都有一個next域指向其後繼結點。

public class QNode {
	public volatile boolean locked = false;
	public QNode next = null;
}
           

       而連結清單的尾節點則通過鎖AtomicReference<QNode>類型的tail成員變量指向,即tail指向加入到申請鎖的隊列中的最近一個線程。

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

	public MCSLock() {
		tail = new AtomicReference<QNode>(null);
		myNode = new ThreadLocal<QNode>() {
			protected QNode initialValue() {
				return new QNode();
			}
		};
	}

	public void lock() {
		QNode qnode = myNode.get();
		QNode pred = tail.getAndSet(qnode);
		if (null != pred) {
			qnode.locked = true;
			pred.next = qnode;
		}
		while (qnode.locked) {
		}
	}

	public void unlock() {
		QNode qnode = myNode.get();
		if (null == qnode.next) {
			if (tail.compareAndSet(qnode, null)) {
				return;
			}
			while (null == qnode.next) {
			}
		}
		qnode.next.locked = false;
		qnode.next = null;
	}
}
           

       當一個線程申請鎖時:

a)首先會執行個體化一個QNode節點,并将其設定為自己的本地線程變量myNode;

b)然後通過調用AtomicReference的getAndSet()方法,将myNode設定為隊列的最後一個節點,并傳回其前驅節點

c)接着判斷前面是否已經有其他線程加入到鎖的等待隊列中,即前驅節點是否為空。如果前驅節點為空,則說明自己是第一個加入到鎖的等待隊列中的線程,執行自旋,由于locked域初始值為false,是以能立即退出自旋,擷取到鎖;

d)如果前驅節點非空,則首先将myNode的locked域設定為true,表明自己正在等待擷取鎖;同時将前驅結點的next域指向myNode。

e)最後該線程一直在自己節點的locked域自旋,直到locked域變為false,即前驅節點釋放了鎖。

        當一個線程釋放鎖時,會處于以下三種情況之一中:

a)此刻沒有其它線程正在申請鎖,即目前節點的next域指向null,且tail指向目前節點:此種情況通過調用AtomicReference的compareAndSet()方法,将tail指向null;然後直接傳回。

b)此刻有其他線程正在申請鎖,而且已更新tail域,但還未将前驅節點的next域指向它。即目前節點的next域指向null,且tail不是指向目前節點:此種情況則一直等待其他線程設定前驅節點的next域後,才将後繼節點的locked域設定為false,以便後繼節點退出自旋,進而擷取到釋放的鎖。

c)此刻有其他線程正在申請鎖,而且已更新tail域,而且已将前驅節點的next域指向它。即目前節點的next域非空:此種情況則直接将後繼節點的locked域設定為false,以便後繼節點退出自旋,進而擷取到釋放的鎖。

       下圖闡述了MCS算法中鎖的擷取和釋放過程(引自The art of Multiprocessor Programming一書):

MCS鎖

繼續閱讀