如何設計一個正確的單例模式類

如何設計一個正確的單例模式類

一、大家一般創建的設計單例類

demo 1class A
{
public:
	static A* GetSingleInstance()
	{
		//步驟1
		if(m_instance == NULL)
		{
			//步驟2
			m_instance = new A();
		}
		return m_instance;
	}
	
private:	//構造函數和變量聲明爲private,如果是基類希望被人繼承使用,則聲明爲protect
	A(){};	
	static A* m_instance;
}

A* A::m_instance = NULL;

如上demo所示,這是大家習慣的正常創建單例類的方式。當他僅被使用於單線程,則完全沒問題。但如果用於多線程時,則可能會出現重複new對象的問題!

二、解決多線程訪問單例重複new內存問題

當多線程訪問“demo 1”中的單例時,此時獲取單例的函數爲非線程安全的,所以,會導致多線程異步執行時,出現重複new 對象的問題,這時可以爲其加鎖的方式解決,如下demo 2所示:

demo 2class A
{
public:
	static A* GetSingleInstance()
	{
		std::mutex  mt;
		mt.lock();
		//步驟1
		if(m_instance == NULL)
		{
			//步驟2
			m_instance = new A();
		}
		mt.unlock();
		return m_instance;
	}
	
private:	//構造函數和變量聲明爲private,如果是基類希望被人繼承使用,則聲明爲protect
	A(){};	
	static A* m_instance;
}

A* A::m_instance = NULL;

上述代碼中,相當於給獲取單例函數加了一個函數全局鎖,使該函數爲線程安全函數。但如果是在高併發場景下,這個鎖的性能消耗是很大的!因爲除了第一次真正創建實例外,其餘的操作都是“讀”操作,所以,需要對這個demo再進行改進。

三、使用雙檢查鎖機制,但由於內存reorder機制使線程不安全

根據“demo 2”中鎖描述的方式,則應該在第一次創建實例後,後續獲取實例時儘量不使用鎖,這樣就能解決高併發場景下鎖帶來的性能問題。實現方式如下所示:

demo 3class A
{
public:
	static A* GetSingleInstance()
	{
		//步驟1
		if(m_instance == NULL)
		{
			std::mutex  mt;
			mt.lock();
			if(m_instance == NULL)
			{
				//步驟2
				m_instance = new A();
			}
			mt.unlock();
		}
		
		return m_instance;
	}
	
private:	//構造函數和變量聲明爲private,如果是基類希望被人繼承使用,則聲明爲protect
	A(){};	
	static A* m_instance;
}

A* A::m_instance = NULL;

如“demo 3” 所示,使用雙檢查鎖機制能有效的減少在高併發場景下因爲鎖帶來的性能消耗,但是由於多線程在操作系統指令級別處理時,有可能會產生與代碼的順序不一致的問題,即new對象首先分配一段內存,然後返回地址,然後再初始化,這時就會導致其他線程拿到了一個沒有初始化的對象而產生後續的一些問題,這就是“Memory reorder”現象,詳情參考“https://www.cnblogs.com/cbscan/articles/4122337.html”。所以,使用“demo 3”版本的雙檢查鎖是線程不安全的,大家不要去使用!!

四、C++11徹底解決多線程下鎖消耗問題(這纔是正解!!!)

更新後的demo如下圖所示:

demo 3class A
{
public:
	static A* GetSingleInstance()
	{
		***//C++11 版本之後的跨平臺實現(Jave用volatile)***
		A* temp = m_instance.load(std::memory_order_relaxed);
		std::atomic_trhead_fence(std::memory_order_acquire);	//獲取內存fence
		if(tmp == NULL)
		{
			std::lock_guard<std::mutex> lock(m_mt);
			tmp = m_instance.load(std::memory_order_relaxed);
			
			if(tmp == NULL)
			{
				tmp = new A();
				std::atomic_trhead_fence(std::memory_order_release);	//獲取內存fence
				m_instance.store(tmp, std::memory_order_relaxed);
			}
			
		}
		
		return m_instance;
	}
	
private:	//構造函數和變量聲明爲private,如果是基類希望被人繼承使用,則聲明爲protect
	A(){};	
	static std::atomic<A*> m_instance;
	static std;:mutex m_mt;
}

std::atomic<A*> A::m_instance = NULL;
std::mutex A::m_mt;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章