最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解如何使用C++寫一個線程安全的單例模式

 更新時間:2022年10月20日 14:10:44   作者:寒星n號  
這篇文章主要為大家詳細介紹了如何使用C++寫一個線程安全的單例模式,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解一下

單例模式的簡單實現(xiàn)

單例模式大概是流傳最為廣泛的設計模式之一了。一份簡單的實現(xiàn)代碼大概是下面這個樣子的:

class singleton
{
public:
	static singleton* instance()
	{
		if (inst_ != nullptr) { 
			inst_ = new singleton();
		}
		return inst_;
	}
private:
	singleton(){}
	static singleton* inst_;
};

singleton* singleton::inst_ = nullptr;

這份代碼在單線程的環(huán)境下是完全沒有問題的,但到了多線程的世界里,情況就有一點不同了。考慮以下執(zhí)行順序:

  • 線程1執(zhí)行完if (inst_ != nullptr)之后,掛起了;
  • 線程2執(zhí)行instance函數(shù):由于inst_還未被賦值,程序會inst_ = new singleton()語句;
  • 線程1恢復,inst_ = new singleton()語句再次被執(zhí)行,單例句柄被多次創(chuàng)建。

所以,這樣的實現(xiàn)是線程不安全的。

有問題的雙重檢測鎖

解決多線程的問題,最常用的方法就是加鎖唄。于是很容易就可以得到以下的實現(xiàn)版本:

class singleton
{
public:
	static singleton* instance()
	{
		guard<mutex> lock{ mut_ };
		if (inst_ != nullptr) {
			inst_ = new singleton();
		}
		return inst_;
	}
private:
	singleton(){}
	static singleton* inst_;
	static mutex mut_;
};

singleton* singleton::inst_ = nullptr;
mutex singleton::mut_;

這樣問題是解決了,但性能上就不那么另人滿意,畢竟每一次使用instance都多了一次加鎖和解鎖的開銷。更關鍵的是,這個鎖也不是每次都需要??!實際我們只有在創(chuàng)建單例實例的時候才需要加鎖,之后使用的時候是完全不需要鎖的。于是,有人提出了一種雙重檢測鎖的寫法:

...
	static singleton* instance()
	{
		if (inst_ != nullptr) {
			guard<mutex> lock{ mut_ };
			if (inst_ != nullptr) {
				inst_ = new singleton();
			}
		}
		return inst_;
	}
...

我們先判斷一下inst_是否已經(jīng)初始化了,如果沒有,再進行加鎖初始化流程。這樣,雖然代碼看上去有點怪異,但好像確實達到了只在創(chuàng)建單例時才引入鎖開銷的目的。不過遺憾的是,這個方法是有問題的。Scott Meyers 和 Andrei Alexandrescu 兩位大神在C++ and the Perils of Double-Checked Locking 一文中對這個問題進行了非常詳細地討論,我們在這兒只作一個簡單的說明,問題出在:

	inst_ = new singleton();

這一行。這句代碼不是原子的,它通常分為以下三步:

  • 調用operator new為singleton對象分配內(nèi)存空間;
  • 在分配好的內(nèi)存空間上調用singleton的構造函數(shù);
  • 將分配的內(nèi)存空間地址賦值給inst_。

如果程序能嚴格按照1-->2-->3的步驟執(zhí)行代碼,那么上述方法沒有問題,但實際情況并非如此。編譯器對指令的優(yōu)化重排、CPU指令的亂序執(zhí)行(具體示例可參考《【多線程那些事兒】多線程的執(zhí)行順序如你預期嗎?》)都有可能使步驟3執(zhí)行早于步驟2??紤]以下的執(zhí)行順序:

  • 線程1按步驟1-->3-->2的順序執(zhí)行,且在執(zhí)行完步驟1,3之后被掛起了;
  • 線程2執(zhí)行instance函數(shù)獲取單例句柄,進行進一步操作。

由于inst_在線程1中已經(jīng)被賦值,所以在線程2中可以獲取到一個非空的inst_實例,并繼續(xù)進行操作。但實際上單例對像的創(chuàng)建還沒有完成,此時進行任何的操作都是未定義的。

現(xiàn)代C++中的解決方法

在現(xiàn)代C++中,我們可以通過以下幾種方法來實現(xiàn)一個即線程安全、又高效的單例模式。

使用現(xiàn)代C++中的內(nèi)存順序限制

現(xiàn)代C++規(guī)定了6種內(nèi)存執(zhí)行順序。合理的利用內(nèi)存順序限制,即可避免代碼指令重排。一個可行的實現(xiàn)如下:

class singleton {
public:
	static singleton* instance()
	{
		singleton* ptr = inst_.load(memory_order_acquire);
		if (ptr == nullptr) {
			lock_guard<mutex> lock{ mut_ };
			ptr = inst_.load(memory_order_relaxed);
			if (ptr == nullptr) {
				ptr = new singleton();
				inst_.store(ptr, memory_order_release);
			}
		}
	
		return inst_;
	}
private:
	singleton(){};
	static mutex mut_;
	static atomic<singleton*> inst_;
};

mutex singleton::mut_;
atomic<singleton*> singleton::inst_;

來看一下匯編代碼:

可以看到,編譯器幫我們插入了必要的語句來保證指令的執(zhí)行順序。

使用現(xiàn)代C++中的call_once方法

call_once也是現(xiàn)代C++中引入的新特性,它可以保證某個函數(shù)只被執(zhí)行一次。使用call_once的代碼實現(xiàn)如下:

class singleton
{
public:
	static singleton* instance()
	{
		if (inst_ != nullptr) {
			call_once(flag_, create_instance);
		}
		return inst_;
	}
private:
	singleton(){}
	static void create_instance()
	{
		inst_ = new singleton();
	}
	static singleton* inst_;
	static once_flag flag_;
};

singleton* singleton::inst_ = nullptr;
once_flag singleton::flag_;

來看一下匯編代碼:

可以看到,程序最終調用了__gthrw_pthread_once來保證函數(shù)只被執(zhí)行一次。

使用靜態(tài)局部變量

現(xiàn)在C++對變量的初始化順序有如下規(guī)定:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

所以我們可以簡單的使用一個靜態(tài)局部變量來實現(xiàn)線程安全的單例模式:

class singleton
{
public:
	static singleton* instance()
	{
		static singleton inst_;
		return &inst_;
	}
private:
	singleton(){}
};

來看一下匯編代碼:

可以看到,編譯器已經(jīng)自動幫我們插入了相關的代碼,來保證靜態(tài)局部變量初始化的多線程安全性。

以上就是詳解如何使用C++寫一個線程安全的單例模式的詳細內(nèi)容,更多關于C++線程安全的單例模式的資料請關注腳本之家其它相關文章!

相關文章

  • C++學習筆記之初始化列表

    C++學習筆記之初始化列表

    初始化列表是類中構造函數(shù)的一部分,用于實例化類中變量時賦初值,下面這篇文章主要給大家介紹了關于C++學習筆記之初始化列表的相關資料,需要的朋友可以參考下
    2023-04-04
  • Windows下CMake的下載與安裝過程

    Windows下CMake的下載與安裝過程

    CMake是一個跨平臺的安裝(編譯)工具,可以用簡單的語句來描述所有平臺的安裝(編譯過程),這篇文章主要介紹了Windows下CMake的下載與安裝,需要的朋友可以參考下
    2022-02-02
  • C語言實現(xiàn)循環(huán)鏈表

    C語言實現(xiàn)循環(huán)鏈表

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)循環(huán)鏈表,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • C++基于遞歸算法解決漢諾塔問題與樹的遍歷功能示例

    C++基于遞歸算法解決漢諾塔問題與樹的遍歷功能示例

    這篇文章主要介紹了C++基于遞歸算法解決漢諾塔問題與樹的遍歷功能,簡單描述了遞歸算法的原理,并結合實例形式分析了基于遞歸算法解決漢諾塔問題與數(shù)的遍歷相關操作技巧,需要的朋友可以參考下
    2017-11-11
  • C++ Vector迭代器失效問題的解決方法

    C++ Vector迭代器失效問題的解決方法

    最近我學習了C++中的迭代器失效問題,迭代器失效問題是非常非常重要的,所以特意整理出來一篇文章供我們一起復習和學習
    2022-08-08
  • c語言中數(shù)組名a和&a詳細介紹

    c語言中數(shù)組名a和&a詳細介紹

    其實這兩個東西挺難理解的,應該也沒有那么重要,了解一下好了,主要還是要多多理解數(shù)組指針的運算
    2013-08-08
  • C++深入淺出講解隱藏this指針的用法

    C++深入淺出講解隱藏this指針的用法

    在C++中,每一個對象都能通過this指針來訪問自己的地址。this?指針是所有成員函數(shù)的隱含參數(shù)。因此,在成員函數(shù)內(nèi)部,它可以用來指向調用對象
    2022-05-05
  • C語言超詳細梳理排序算法的使用

    C語言超詳細梳理排序算法的使用

    這篇文章主要介紹了C語言完成排序的實例,在C語言基本類型的排序中特別有用,下面我們一起進入文章學習更詳細的內(nèi)容吧,需要的朋友可以參考下
    2022-03-03
  • MFC繪制不規(guī)則窗體的方法

    MFC繪制不規(guī)則窗體的方法

    這篇文章主要介紹了MFC繪制不規(guī)則窗體的方法,涉及MFC窗體操作的相關技巧,需要的朋友可以參考下
    2015-05-05
  • C語言文件操作函數(shù)大全(超詳細)

    C語言文件操作函數(shù)大全(超詳細)

    本篇文章是對C語言中的文件操作函數(shù)進行了詳細的總結分析,需要的朋友參考下
    2013-05-05

最新評論

奇台县| 德惠市| 巴马| 太仓市| 东港市| 双江| 西青区| 平原县| 台南市| 茶陵县| 彰化县| 台南市| 壶关县| 三明市| 明水县| 永济市| 刚察县| 邵阳县| 凭祥市| 通海县| 永靖县| 尼玛县| 凤翔县| 读书| 开原市| 定兴县| 房山区| 北京市| 文安县| 泰和县| 吉林省| 浦江县| 八宿县| 乌兰察布市| 大厂| 香港 | 香港| 红安县| 聊城市| 会泽县| 义乌市|