C++ std::call_once單次調用

在相似「多線程中使用單例的懶漢式初始化」場景中,爲了提升效率,一般不是簡單的鎖定,這會致使沒必要要的線程序列化。許多人都試圖想出一個更好的實現方法,包括臭名昭著的雙重檢查鎖定(Double-Checked Locking)模式(DCLP)ios

#include <iostream>
#include <thread>
#include <mutex>

class Singleton
{
public:
	static Singleton* getInstance() {
		if (!instancePtr) {
			std::lock_guard<std::mutex> guard(initMutex);
			if (!instancePtr) {
				instancePtr = new Singleton();
				std::cout << "init" << std::endl;
			}
		}
		return instancePtr;
	}
private:
	Singleton() = default;
	Singleton(const Singleton&) = default;
	Singleton& operator=(const Singleton&) = default;
	//garbo類用於自動釋放單例實例(棧變量在析構函數中析構instance)
	class Garbo {
	public:
		~Garbo() {
			if (Singleton::instancePtr) {
				delete Singleton::instancePtr;
			}
		}
	};
private:
	static Singleton* instancePtr;
	static std::mutex initMutex;
	static Garbo delGarbo;
};

Singleton* Singleton::instancePtr = nullptr;
std::mutex Singleton::initMutex;
Singleton::Garbo Singleton::delGarbo = Singleton::Garbo();

int main()
{
	{
		Singleton* inst_1 = Singleton::getInstance();
		std::thread t2([=]() {
			Singleton* inst_2 = Singleton::getInstance();
			});
		std::thread t3([=]() {
			Singleton* inst_3 = Singleton::getInstance();
			});
		t2.join();
		t3.join();
	}
	system("pause");
	return 0;
}

雙重檢測鎖定有一個在代碼層面看不到的問題,當執行 instance=new Singleton; 時,實際上分爲三個步驟:多線程

  • 第一步:爲Singleton對象分配一片內存
  • 第二步:構造一個Singleton對象,存入已分配的內存區
  • 第三步:將instance指向這片內存區

 實際上,編譯器有時會交換步驟2和步驟3的執行順序。可是本文並不打算複製粘貼這一部份內容(太長了),感興趣的能夠參考:併發

在C++11中能夠使用靜態初始化器完成單例初始化的操做,C++11標準規定,若是控制進入申明同時變量將被初始化的時候,那麼併發執行將會等到初始化的完成。函數

Singleton& getInstance() {
		static Singleton instance;
		return instance;
	}

 此外,C++11還提供了std::call_once準確執行一次可調用對象,即便同時從多個線程調用。spa

template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );.net

若在調用 call_once 的時刻,若 flag 指示已經調用了 f ,則 call_once 當即返回。藉助 call_once 修改下上面的單例:線程

class Singleton
{
public:
	static Singleton* getInstance() {
		std::call_once(initFlag, []() {
			instancePtr = new Singleton();
			std::cout << "init" << std::endl;
			});
		return instancePtr;
	}

private:
	Singleton() = default;
private:
	static Singleton* instancePtr;
	static std::once_flag initFlag;
};

Singleton* Singleton::instancePtr = nullptr;
std::once_flag Singleton::initFlag;