3
void undefined_behaviour_with_double_checked_locking() 
{ 
    if(!resource_ptr)         #1 
    { 
     std::lock_guard<std::mutex> lk(resource_mutex); #2 
     if(!resource_ptr)        #3 
     { 
      resource_ptr.reset(new some_resource);  #4 
     } 
    } 
    resource_ptr->do_something();      #5 
} 

如果一個線程看到另一個線程寫入的指針,它可能不是 看到some_resource的新創建的實例,從而導致調用 到do_something()操作上不正確的值。這是一個 的示例,該競爭條件的類型被C++標準定義爲數據爭用 ,因此被指定爲未定義的行爲。解釋競態條件雙重檢查鎖定

問題>我已經看到了爲什麼代碼具有雙重檢查鎖定的問題導致了比賽狀態,在上述說明。但是,我仍然很難理解問題所在。也許一個具體的雙線程分步工作流程可以幫助我真正理解上述代碼的競爭問題。

一個接書中提到的解決方案如下:

std::shared_ptr<some_resource> resource_ptr; 
std::once_flag resource_flag; 

void init_resource() 
{ 
    resource_ptr.reset(new some_resource); 
} 
void foo() 
{ 
    std::call_once(resource_flag,init_resource); #1 
    resource_ptr->do_something(); 
} 
#1 This initialization is called exactly once 

任何意見,歡迎 - 謝謝

+1

閱讀[這個答案]的第二部分(http://stackoverflow.com/a/367690/14065)的一些非顯而易見的問題。 – 2011-12-20 07:03:39

回答

4

最簡單的問題場景是在some_resource的初始化不依賴於resource_ptr的情況下。在這種情況下,編譯器可以在完全構造some_resource之前自由地將值分配給resource_ptr

例如,如果你想new some_resource操作爲包括兩個步驟:

  • 分配的內存some_resource
  • 初始化some_resource(對於這個討論,我要做出的簡化假設這個初始化不能拋出異常)

然後,你可以看到,編譯器可以執行的代碼互斥保護區,如:

1. allocate memory for `some_resource` 
2. store the pointer to the allocated memory in `resource_ptr` 
3. initialize `some_resource` 

現在很清楚,如果另一個線程執行步驟2和3之間的函數,然後resource_ptr->do_something()可以同時some_resource尚未初始化調用。

請注意,在某些處理器體系結構中,這種重新排序也可能發生在硬件中,除非適當的內存屏障已到位(並且此類屏障將由互斥體實現)。

7

在此情況下(取決於.reset實施和!)有可能當線程1通過初始化resource_ptr中途暫停/切換時,會成爲問題。線程2然後出現,執行第一次檢查,看到指針不爲空,並跳過鎖定/完全初始化檢查。然後它使用部分初始化的對象(可能導致不好的事情發生)。線程1然後返回並完成初始化,但已經太晚了。

+0

所以你的關鍵是,如果'.reset'被燒烤並暫停,競賽問題就會發生。換句話說,如果'.reset'不是一個原子操作,那麼問題就會發生。 – q0987 2011-12-20 05:11:08

+0

是的,重置和!操作員是關鍵。如果你看看這些方法的實現,還要記住編譯器可能會爲你重新排列一些語句(這是導致重複檢查鎖定問題的常見原因)。 – jnnnnn 2011-12-20 06:30:03