Double checked locking,兩次檢查的鎖

最開始知道這個內容是在和室友聊天的時候發現的,它說國內某互聯網公司中有一道筆試題,需要當場寫出無鎖隊列的代碼。當時對於這個概念還是一知半解,後來他說陳皓博客上有相關的內容,本着好學的精神就去一窺究竟:無鎖隊列的實現


基本思想是利用編譯器或者語言提供的原子操作,CAS(compare and swap或者compare and set)。

bool compare-and-swap(Adderss addr, Value old, Value new)
{
    if (*addr = old) {
        *addr = new;
        return true;
    }
    return false;
}

其實原子操作是因爲並行編程的問題而更加受到重視。因爲在傳統的單核CPU上,在同一時刻只有一個線程(或者進程)在運行。不同線程之間不會產生競爭,也就不就引入所謂的鎖或者其他保證在臨界區的互斥訪問。


在多線程的環境下對於資源的分配需要給予足夠的重視,一方面變量被重複初始化或者造成資源的丟失。在單例模式中:

class Singleton
{
public:
    static Singleton *instance (void)
    {
        if (instance_ == 0)
        // critical section
        instance_  = new Singleton;
        return instance_;
    }
   void method(void);
private:
    static Singleton *instance_;
};

在上面的critical section標記中,當多個線程同時到達這個地方時,instance_可能會被多次初始化。


簡單直接的解決方法即枷鎖

class Singleton
{
public:
	static Singleton *instacne(void)
	{
	  // Constructor of guard acquires
	  // lock_ automatically
	  Guard<Mutex> guard (lock_);
	  
	  // only one thread in the 
	  // critical section in a time
	  
	  if (instance_ == 0)
	    instance_ = new Singleton;
		
	  return instance_;
	 }
private:
	static Mutex lock_;
	static Singleton *instance_;
};

簡單粗暴的加鎖方式使得訪問instance的效率大大降低,即使是訪問instance_也需要獲得鎖。Schmidt等人提出一種Double checked locking方式,如下:

class Singleton
{
public:
	static Singleton *instacne(void)
	{
	  // first check
	  
	  if (instance_ == 0)
	  {
		Guard<Mutex> guard (lock_);
		if (instance_ == 0)
			instance_ = new Singleton;
	  // only one thread in the 
	  // critical section in a time	  
	 }
	 return instance_;
	}
private:
	static Mutex lock_;
	static Singleton *instance_;
};

這種雙加鎖的方式具有一定的應用場景,但是也不是萬能的。看一段Java的利用double checked locking的代碼:

public void addResult(String uri, ProfilerData data)
{
	Long key = new Long(data.getKey(uri));
	
	// critical section
	ProfilerResult result = (ProfilerResult)results.get(key);
	
	if (result == null)
	{
		synchronized(results)
		{
			if ((result == (ProfilerResult)results.get(key)) == null)
				results.put(key,result = new ProfilerResult(uri, RESULTS_COUNT));
		}
	}
	results.addData(data);
}

注意上述表明的critical section區域。這句話不是同步的,則後面的判斷就會出錯。所以經典的方法在使用時必須注意實際的應用性。關於並行程序最好是使用公開的算法,因爲一般都受過檢驗。


本文完


參考:

[1] Schmidt等 double checked locking

[2] Code quality

[3] C++ concurrency in action, Jolt獎的書




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章