[英]why c++ singleton need memory_order_acquire
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); //<--1
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;
assert(tmp != nullptr);
std::atomic_thread_fence(std::memory_order_release); //<--2
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
這里有一個常見的 c++ singleton 實現, 2
中有一個release fence
(如上標記),很容易理解,它防止重新排序new Singleton
,如果沒有這個圍欄,另一個線程可能會在沒有執行構建的情況下獲得實例;
令我疑惑的是1
中的acquire fence
, release fence
承諾Singleton構造已經執行完然后存到m_instance,這里我們fetch instance的時候不執行構造就得不到instance,為什么還需要acquire fence
1
?
而且,我們可以用 m_instance 操作內存順序替換atomic_thread_fence
嗎,它們是一樣的嗎? (如下圖所示)
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(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;
assert(tmp != nullptr);
m_instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
是的,具有對m_instance
操作的獲取/釋放順序的第二個版本是正確的,並且等同於第一個版本。 事實上,它甚至比第一個版本更可取,因為柵欄會影響所有前面的(獲取)/后續(釋放)原子操作,但您只需要同步m_instance
上的操作。 這就是為什么在某些架構上顯式柵欄速度較慢的原因。
為什么首先需要獲取/釋放? 因為您需要在創建 singleton 和使用 singleton 之間建立先行關系以避免數據競爭。 假設如下:
Singleton::getInstance()
並初始化; 這涉及創建一個新的 object 並將指針存儲在m_instance
中Singleton::getInstance()
,觀察線程1寫入的指針線程 2 最喜歡取消引用指針以訪問它指向的 object,並且此訪問很可能是非原子的。 因此,您有兩個對 object 的非原子訪問 - 一個在創建期間,一個在使用 object 時。如果這些不是按之前發生的關系排序,那么這就是數據競爭。
那么我們如何建立happens-before關系呢? 通過使用memory_order_release
memory_order_acquire
它。 當獲取加載操作觀察到發布存儲寫入的值時,加載與存儲同步,從而建立先行關系。 此外,object 的構造在存儲之前排序,並且加載在取消引用之前排序(之前排序也意味着發生之前),並且由於發生之前是可傳遞的,因此構造發生在取消引用之前。
有關 C++ memory model 的更多詳細信息,我推薦我與他人合着的這篇論文: Memory C/C++ 程序員模型
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.