在類似“多線程中使用單例的懶漢式初始化”場景中,爲了提高效率,通常不是簡單的鎖定,這會導致不必要的線程序列化。許多人都試圖想出一個更好的實現方法,包括臭名昭著的雙重檢查鎖定(Double-Checked Locking)模式(DCLP)。
#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準確執行一次可調用對象,即使同時從多個線程調用。
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
若在調用 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;