單例是最常用的設計模式,但基本實現非線程安全:
Singleton* Singleton::instance() {
if (pInstance == 0) { // Line 1
pInstance = new Singleton; // Line 2
}
return pInstance;
}
多線程下,線程 A 執行 Line 1 判斷為空後被掛起,線程 B 執行完創建對象返回,線程 A 恢復執行 Line 2 再次創建,導致兩個實例。
Singleton* Singleton::instance() {
Lock lock;
if (pInstance == 0) pInstance = new Singleton;
return pInstance;
}
每次調用都加鎖,開銷過大。實際只需首次創建時加鎖。
Singleton* Singleton::instance() {
if (pInstance == 0) { // 1st test
Lock lock;
if (pInstance == 0) { // 2nd test
pInstance = new Singleton;
}
}
return pInstance;
}
看似完美:首次檢查無鎖,只有為空時才加鎖再檢查一次。但存在致命缺陷。
pInstance = new Singleton 實際分三步:
operator new(sizeof(Singleton))new (pInstance) SingletonpInstance = ...編譯器為優化可能交換步驟 2 和 3(先賦值指針,再構造對象)。若線程 A 執行完 1、3 後被掛起,此時 pInstance != NULL 但對象未構造完成。線程 B 進入首次檢查發現非空,直接返回未構造完成的指針,解引用導致崩潰。
Singleton& Singleton::instance() {
static Singleton instance; // C++11 保證線程安全初始化
return instance;
}
std::once_flag flag;
Singleton* Singleton::instance() {
std::call_once(flag, []{ pInstance = new Singleton; });
return pInstance;
}
std::atomic pInstance{nullptr};
std::mutex mtx;
Singleton* Singleton::instance() {
Singleton* tmp = pInstance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard lock(mtx);
tmp = pInstance.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new Singleton;
pInstance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
DCLP 在無內存模型保證的語言中本質上是破碎的。現代 C++ 應優先使用靜態局部變量或 std::call_once,簡單、安全、零開銷(首次調用外)。