雙重檢查鎖定模式(Double-Checked Locking Pattern, DCLP)是單例模式在多線程環境下的經典優化。C++11 前無標準可移植實現,C++11 引入原子操作與內存模型後終得正解。本文詳細解析 DCLP 原理、C++11 實現與 Mintomic 移植方案。
單例模式最簡單的線程安全寫法是全程加鎖,但實例創建後後續訪問無需鎖。DCLP 思想:先無鎖檢查,若為空再加鎖,鎖內再次檢查並創建。
// 有缺陷的伪代码
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) { // 第一次檢查(無鎖)
Lock lock;
if (m_instance == nullptr) { // 第二次檢查(有鎖)
m_instance = new Singleton; // 危險:指針賦值可能早于構造函數完成
}
}
return m_instance;
}
編譯器/CPU 可能將 m_instance = new Singleton 重排為:
m_instance若線程 A 執行到步驟 2,線程 B 進入第一次檢查看到非空指針,直接返回,卻訪問到未完全構造的對象 → 未定義行為。
使用 std::atomic 配合 memory_order_acquire/release 建立同步關係:
#include <atomic>
#include <mutex>
class Singleton {
static std::atomic<Singleton*> m_instance;
static std::mutex m_mutex;
public:
static Singleton* getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 獲取屏障
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release); // 釋放屏障
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
關鍵點:
acquire 屏障確保後續讀取不被重排到它之前。release 屏障確保前寫入(構造函數)不被重排到它之後。Mintomic 是輕量級 C 庫,在不支持 C++11 的舊編譯器(GCC 4.6、VS2010 等)上實現獲取/釋放屏障與原子指針操作,API 與 C++11 風格一致,代碼幾乎無需修改即可移植。
// Mintomic 版本
#include <mintomic/mintomic.h>
mint_atomicPtr_t Singleton::m_instance = {0};
mint_mutex_t Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = (Singleton*)mint_load_ptr_relaxed(&m_instance);
mint_thread_fence_acquire();
if (tmp == NULL) {
mint_mutex_lock(&m_mutex);
tmp = (Singleton*)mint_load_ptr_relaxed(&m_instance);
if (tmp == NULL) {
tmp = new Singleton;
mint_thread_fence_release();
mint_store_ptr_relaxed(&m_instance, tmp);
}
mint_mutex_unlock(&m_mutex);
}
return tmp;
}
C++11 通過標準化內存模型徹底解決了 DCLP 移植難題。理解獲取-釋放語義是編寫正確無鎖代碼的基石。對於舊工具鏈,Mintomic 提供了實用的過渡方案。