欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

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)境下是完全沒有問題的,但到了多線程的世界里,情況就有一點(diǎn)不同了。考慮以下執(zhí)行順序:

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

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

有問題的雙重檢測(cè)鎖

解決多線程的問題,最常用的方法就是加鎖唄。于是很容易就可以得到以下的實(shí)現(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都多了一次加鎖和解鎖的開銷。更關(guān)鍵的是,這個(gè)鎖也不是每次都需要啊!實(shí)際我們只有在創(chuàng)建單例實(shí)例的時(shí)候才需要加鎖,之后使用的時(shí)候是完全不需要鎖的。于是,有人提出了一種雙重檢測(cè)鎖的寫法:

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

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

	inst_ = new singleton();

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

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

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

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

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

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

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

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

現(xiàn)代C++規(guī)定了6種內(nèi)存執(zhí)行順序。合理的利用內(nèi)存順序限制,即可避免代碼指令重排。一個(gè)可行的實(shí)現(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++中引入的新特性,它可以保證某個(gè)函數(shù)只被執(zhí)行一次。使用call_once的代碼實(shí)現(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_;

來看一下匯編代碼:

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

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

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

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

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

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

來看一下匯編代碼:

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

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

相關(guān)文章

  • 深入C++實(shí)現(xiàn)函數(shù)itoa()的分析

    深入C++實(shí)現(xiàn)函數(shù)itoa()的分析

    本篇文章是對(duì)C++實(shí)現(xiàn)函數(shù)itoa()進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Vs2022環(huán)境下安裝低版本.net framework的實(shí)現(xiàn)步驟

    Vs2022環(huán)境下安裝低版本.net framework的實(shí)現(xiàn)步驟

    本文主要介紹了Vs2022環(huán)境下安裝低版本.net framework的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • C++實(shí)現(xiàn)無重復(fù)字符的最長子串

    C++實(shí)現(xiàn)無重復(fù)字符的最長子串

    本文主要介紹了C++實(shí)現(xiàn)無重復(fù)字符的最長子串,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • C語言高級(jí)教程之變長數(shù)組詳解

    C語言高級(jí)教程之變長數(shù)組詳解

    這篇文章主要介紹了C語言中變長數(shù)組的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • C 語言程序結(jié)構(gòu)示例解析

    C 語言程序結(jié)構(gòu)示例解析

    本文主要講解C 語言程序結(jié)構(gòu),這里提供簡單的示例來講解C 語言程序的結(jié)構(gòu),有利于剛剛學(xué)習(xí)C 語言的同學(xué)理解程序結(jié)構(gòu)
    2016-08-08
  • C++教程之a(chǎn)rray數(shù)組使用示例詳解

    C++教程之a(chǎn)rray數(shù)組使用示例詳解

    這篇文章主要為大家介紹了C++教程之a(chǎn)rray數(shù)組使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • C++/Php/Python/Shell 程序按行讀取文件或者控制臺(tái)的實(shí)現(xiàn)

    C++/Php/Python/Shell 程序按行讀取文件或者控制臺(tái)的實(shí)現(xiàn)

    下面小編就為大家?guī)硪黄狢++/Php/Python/Shell 程序按行讀取文件或者控制臺(tái)的實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-03-03
  • 詳解C++11中的線程鎖和條件變量

    詳解C++11中的線程鎖和條件變量

    C++ 11允許開發(fā)者們以標(biāo)準(zhǔn)的、不依賴于平臺(tái)的方式編寫多線程程序。這篇文章概述了標(biāo)準(zhǔn)庫對(duì)于線程和同步操作機(jī)制的支持。這些都是非常重要的知識(shí),希望讀者們可以認(rèn)真看一下
    2021-06-06
  • VC對(duì)自定義資源加密解密(AES)的詳解

    VC對(duì)自定義資源加密解密(AES)的詳解

    本篇文章是對(duì)VC對(duì)自定義資源加密解密(AES)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • OpenCV實(shí)現(xiàn)繪制輪廓外接矩形

    OpenCV實(shí)現(xiàn)繪制輪廓外接矩形

    這篇文章主要為大家詳細(xì)介紹了OpenCV實(shí)現(xiàn)繪制輪廓外接矩形的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-12-12

最新評(píng)論