c++中的double check


  double check 在併發編程中經常⻅到,很多⼈會⽤,但是僅有double check還是不夠的,會產⽣reorder的問題;拿單例模式中的double check中get_instance接⼝來舉例,如下:

<code>singleton& get_instance() { 
if (m_instance_ptr == nullptr) {
std::lock_guard<:mutex> mu_lock(singleton::m_mutex);
if (m_instance_ptr == nullptr) {
m_instance_ptr = new singleton();
}
}
return *m_instance_ptr;
}/<code>

  錯誤有可能發⽣在 m_instance_ptr = new singleton() ,這條語句並不是原⼦操作,我們可以簡單理解為(分配內存,構造函數調⽤,賦值給 m_instance_ptr )三步。

  ⽽這三部分有可能會在編譯器中或者執⾏單元中優化的時候改變順序,對於臨界區的代碼來說沒有問題,但是當順序改變成為(分配內存,賦值給 m_instance_ptr ,構造函數調⽤)時,加上第⼀層check就有問題;當執⾏到賦值給 m_instance_prt 時,這時還沒有調⽤構造函數,另⼀個併發執⾏到第⼀個check,直接返回了未執⾏構造函數的對象,將出現為定義的⾏為。⽽在⼤多數的語⾔中都會有這種reorder的問題。

為了應對此類問題,java引⼊volidate關鍵字;但是c++卻沒有能達到同等效果的關鍵字;

⽽在c++11之後我們可以有⼏種⽅法來解決這個問題;

  • 使⽤std::call_once
<code>std::once_flag m_initflag;
singleton& get_instance() {
std::call_once(m_initflag, [&](){m_instance_ptr = new singleton();});
return *m_instance_ptr;
}/<code>
  • 靜態局部變量

According to the standard, §6.7 [stmt.dcl] p4

Visual Studio: supported since Visual Studio 2015

GCC: supported since GCC 4.3

<code>singleton& get_instance() {
static singleton s;
return s;
}/<code>
  • 原⼦變量+內存序
<code>std::atomic<singleton> m_instance;
singleton* get_instance() {
singleton* tmp = m_instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<:mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new singleton;
m_instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}/<singleton>/<code>

參考:

C++ Singleton design pattern

Double-check locking issues, c++

Is Meyers' implementation of the Singleton pattern thread safe?


分享到:


相關文章: