2014-09-12 71 views
1

我讀併發編程C++鎖定模式和整個這一段代碼來了。這本書提到了討厭的競賽條件的可能性。雙重檢查的C++並行編程

void undefined_behaviour_with_double_checked_locking(){ 

if(!resource_ptr){  //<1> 
    std::lock_guard<std::mutex> lk(resource_mutex); 
    if(!resource_ptr){  //<2> 
     resource_ptr.reset(new some_resource);  //<3> 
    } 
} 

resource_ptr->do_something();  //<4> 

} 

這裏是該書的解釋引用。然而,我只是不能拿出一個真實的例子。我想知道這裏有人能幫助我。

不幸的是,這種模式是臭名昭著的一個原因:它具有討厭的競爭條件 潛力,因爲讀鎖外 < 1>未與被另一個線程內 的完成寫同步鎖< 3>。因此,這將創建一個覆蓋不 只是指針本身也是指向的對象中的競爭條件;即使 線程看到由另一個線程寫入的指針,它也可能不會看到新創建的some_resource實例的 ,從而導致對不正確的值進行操作的 4>操作。

+0

這句話似乎很好地解釋了它......什麼算作「真實的例子」?您是否在尋找關於代碼如何失敗的更詳細的解釋? – dlf 2014-09-12 19:51:02

+0

這裏有什麼讓我感到困惑的是,在兩個線程都調用這個函數的情況下,情況似乎沒有問題,我們也沒有看到問題......所以我想知道真正的問題是什麼.. @ dlf – Decipher 2014-09-12 19:54:26

回答

5

你不顯示什麼resource_ptr是不過從解釋的理由似乎是,「!resource_ptr」(鎖外)和「resource_ptr.reset」(內鎖)不atmoic和不相互同步。

用例是:

  1. 線程1進入的方法,該看到resource_ptr不 填充,進入鎖定並且在 resource_ptr.reset的中間。
  2. thread2進入方法,並且當 檢查!resource_ptr可能會看到它爲set但resource_ptr可能不完全配置爲使用。
  3. 線程2落空執行「resource_ptr-> do_something()」,並可能看到resource_ptr不一致的狀態和糟糕的事情可能發生。
+0

一般來說, #2不正確。 ''resource_ptr'不應該被設置,直到'some_resource :: operator new'返回,並且該表達式負責分配內存和構造對象(並且在返回之前會這樣做)。請參閱http://www.cplusplus.com/reference/new/operator%20new/,以粗略描述「新」陳述所執行的操作。通過啓用編譯器優化,任何事情都是可能的。你更有可能看到兩個線程都將'resource_ptr'視爲未設置,分配一個新的'some_resource',並繼續通過該函數(即使'reset'調用刪除一個)。 – Wug 2014-09-12 21:32:04

1

我推薦你讀這個:http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

無論如何,它的要點是:編譯器可以自由地重新排序操作,只要它們在單線程情況下以程序的順序執行。最重要的是,一些CPU架構與他們的指令執行順序一樣自由。所以,在some_resource的構造函數完成之前,技術上resource_ptr可以被修改爲指向新分配的內存。另一個線程可能在那個時候看到resource_ptr不爲空,並嘗試使用還未完全構建的實例。

使用智能指針來代替原始指針可能使這種可能性較小,但並不排除這種可能性AFAIK。

+1

由於函數調用,'new'在執行'reset'(改變指針)之前被排序。這不應該確保該對象在該點完全構建嗎? (這種情況與使用指針分配的通常情況不同。) – celtschk 2014-09-12 19:59:29

+0

我想知道這一點:即使用簡單的智能指針會對整體有什麼影響。從我所知道的事實可以看出,除非原子或同步對象發揮作用,否則在函數調用之前分配和構造的順序不會超越單線程情況。 – heinrichj 2014-09-12 20:02:37

0

潛在的問題是,寫resource_ptr不是原子(該reset調用中)。假設resource_ptr是一個全局變量或靜態變量,在我們到達此處之前(或以其他方式)以NULL值開始初始化,但除非對象some_resource已被完全分配和構造,否則它絕不會導致線程崩潰,指向這個新對象的指針是0x123456789,那麼當另一個線程執行if (!resource_ptr)測試時,理論上可能resource_ptr的值爲0x12340000,並且使用該值(尤其是使用別名時更可能)。如果resource_ptr是一個原子變量,那麼這段代碼就可以。

如果一個程序可以保證第一次調用這個代碼,那麼只有一個線程正在運行(也就是說,在任何其他線程創建之前第一次調用將從main()中),那麼這也可以正常工作,因爲一旦初始化,if測試將總是通過,導致只有多個線程正在運行時纔讀取到resource_ptr。在這種情況下,您不需要if區塊內的鎖,而且您也不允許在任何地方寫resource_ptr其他區域。