程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 從C++單例形式到線程平安詳解

從C++單例形式到線程平安詳解

編輯:關於C++

從C++單例形式到線程平安詳解。本站提示廣大學習愛好者:(從C++單例形式到線程平安詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是從C++單例形式到線程平安詳解正文


先看一個最復雜的教科書式單例形式:

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		if (NULL == ps)
		{//tag1
			ps = new CSingleton;
		}
		return ps;
	}

private:
	CSingleton(){}
	CSingleton & operator=(const CSingleton &s);
	static CSingleton* ps;
};

CSingleton* CSingleton::ps = NULL;

有2個要點:

1.private的結構函數和=操作符,用於避免類外的實例化和被復制;

2.static的類指針和get辦法。

在大少數單線程狀況下,以上代碼大都會運轉得很好,除非遇到中綴:

1.當順序運轉到tag1 處觸發了中綴;
2.中綴處置順序恰調用的也是getInstance函數。

可想而知,這和多線程的狀況相似,假定線程A 運轉到tag1處,還沒來得及new,此時ps依然是NULL,線程B(或中綴處置順序) 同時也運轉到此經過if判別,那麼將會實例化2個CSingleton對象,顯然是不對的。

為理解決上述問題,自但是然,最容易想到也最常用的辦法是加鎖,因而getInstance改成這樣:

	static CSingleton* getInstance()
	{
		lock();//偽代碼
		if (NULL == ps)
		{
			ps = new CSingleton;
		}
		return ps;
	}

加了鎖當前貌似處理了上述問題,但也異樣帶來了新的問題:假如順序四處是諸如:

CSingleton::instance()->aaaa();
CSingleton::instance()->bbbb();
CSingleton::instance()->cccc();

這樣的調用,除了第一次的lock()有用外,前面的都是在做無用功,lock()的代價說大不大,但在某些狀況下還是會進步順序延遲,這對追求完滿的順序猿來說是完全無法承受的。

於是乎,咱想出了一個方法:

	static CSingleton* getInstance()
	{
		if (NULL == ps)//這裡加了次判別,只要第一次才會為true而調用lock()
		{
			lock();//偽代碼
			if (NULL == ps)
			{
				ps = new CSingleton;
			}
		}
		return ps;
	}

很久當前我才知道,這個辦法有個很矮小上的名字,叫做雙重反省鎖定形式,簡稱DCLP(Double Checked Locking Pattern)。

DCLP很好地處理了屢次調用不用要的lock()。

但是,你們以為這樣就完了?too young。。

DCLP在多線程下依然存在2個基本問題:

1.順序的指令執行順序不確定;
2.編譯器優化問題。

先說2,在某些編譯器下,以上的兩個if判別只會執行一個,甚至一個都不執行,緣由是編譯器以為至多有一個if判別是多余的,它自動協助我們優化了代碼。

再說1,ps = new CSingleton; 這條語句會被拆分為這樣的三個步驟執行:

1.為要new的對象開拓一塊內存;
2.結構該對象,填入這塊內存;
3.將ps指針指向這塊內存。

以上三個步驟,2和3的順序是不確定的,能夠先2後3,也能夠先3後2。。。

實踐執行時能夠是這樣的:

	static CSingleton* getInstance()
	{
		if (NULL == ps)
		{
			lock();//偽代碼
			if (NULL == ps)
			{    //偽代碼
				ps = xx;//step 3
				new sizeof(CSingleton);//step 1
				new CSingleton;//step 2
			}
		}
		return ps;
	}

假如編譯器按上述順序執行代碼,思索如下情況:

線程A 執行到step 1還未執行前面的step 2,此時ps非空,但其指向的內存外面的內容還未被結構出來,於此同時線程B 進入這個函數,判別ps非空直接前往ps,但是調用者此時訪問的ps內存實踐內容CSingleton還沒被結構呢,這是一塊地址正確大小正確但外部數據不明的東西,當然會出錯(調用者普通這麼調用:CSingleton::getInstance()->aa();  CSingleton::getInstance()->bb();  CSingleton::getInstance()->cc();........此時的aa,bb,cc是啥玩意兒?)。

這也是為什麼加上volatile關鍵字依然不可以處理同步問題,volatile只處理了編譯器優化問題,卻無法控制機器指令執行順序。

很遺憾的是,C/C++自身在設計時是不思索多線程問題的,也就是說,要處置多線程問題還要順序猿自己想方法填坑。。

說了這麼多,我們要討論的問題依然沒有處理,慶幸的是,C++ 11提供了內存柵欄技術來處理這個問題,這裡不贅述,有興味的讀者可以自己搜索材料看看,不過是一些api調罷了。

那麼,C++ 11 以前的代碼如何處理這個問題呢?很不幸,並沒有很好的處理方案,一種可行的方案是,順序中不要四處這麼調用這個單例對象:

CSingleton::getInstance()->aa(); 
CSingleton::getInstance()->bb();
CSingleton::getInstance()->cc();

而是在順序開端就初始化緩存這個單例對象:

CSingleton* const g_ps = CSingleton::getInstance();//順序一開端就緩存這個單例對象
g_ps->aa();
g_ps->bb();
g_ps->cc();

但是如此帶來的問題是順序一開端就實例化了這個單例對象,對象在整個順序的聲明周期存在,這貌似叫餓漢式,而之前那種叫懶漢式,孰輕孰重,只要依據實踐狀況取捨了。

以上就是為大家帶來的從C++單例形式到線程平安詳解全部內容了,希望大家多多支持~

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved