2017-06-13 83 views
2

我有以下類:C++ 11 const - 我的代碼是否線程安全?

class Object 
{ 
public: 
    Object() {} 

    const std::string& get_name() const 
    { 
    if(_name.empty()) { 
     std::lock_guard<std::mutex> lock(_lock); 

     // Check if its still empty. Some other thread might have gotten here first 
     if(_name.empty()) { 
     //Run expensive operation 
     _name = get_object_name(); 
     } 
    } 

    return _name; 
    } 

private: 
    std::string get_object_name(); // <- Expensive function 

    mutable std::mutex _lock; 
    mutable std::string _name; 
}; 

由於對get_object_name是一個昂貴的功能的事實,我願做一個排序延遲初始化的並且只調用它的第一次get_name()被調用。如果它從未被調用,那麼我不會浪費資源來獲取名稱。

我很擔心第一次致電_name.empty()。我的當前代碼是否保證是線程安全的,還是需要將鎖移動到函數的頂部?

我看了一些香草薩特的會談,特別是this one,在這張幻燈片上出現:

http://i.imgur.com/Jz4luYe.png

導致我相信,到empty()調用是線程安全的。但我的變量(_name)是mutable。這個「const ==線程安全」規則是否仍然適用於此?

get_name()是唯一可以修改_name的功能。

+2

有該模式的名稱:雙檢鎖(https://en.wikipedia.org/維基/雙checked_locking)。它適用於某些環境,而不適用於其他環境。當它不起作用時,原因很微妙。 –

回答

3

不,它不是線程安全的,因爲您在mutex之外訪問_name,這會中斷同步。

一個可能的解決方案是使用標準庫提供的std::call_once機制。

class Object 
{ 
public: 
    Object() {} 

    const std::string& get_name() const 
    { 
    std::call_once(flag, [&] { _name = get_object_name(); }); 

    return _name; 
    } 

private: 
    std::string get_object_name() const; // <- Expensive function 

    mutable std::string _name; 
    mutable std::once_flag flag; 
}; 

這保證不會多次調用get_object_name()。第一次調用將初始化string,併發呼叫將阻塞,直到lambda完成。
同步是完全照顧,這意味着任何線程得到參考string可以安全地從它讀取。

+0

是的,這就是我所害怕的。你的'call_once'解決方案非常酷,不過我喜歡它。我認爲它有一個小的開銷(沒有什麼能夠讓我的程序減慢一些數量級,如果我把它扔到循環中),對嗎? – Mark

+0

@Mark「call_once」的可能實現是_double-checked鎖定模式_。開銷(初始化後)可能相當於原子加載/獲取,這相當於'X86' – LWimsey

+0

上的正常加載,謝謝。 – Mark

0

啓動C++ 11 static變量以線程安全方式進行初始化。 如果得到這個名字的昂貴操作變得static我認爲以下比較好:

class Object 
{ 
public: 
    const std::string& get_name() const 
    { 
     static std::string name = expensive_get_name(); 
     return name; 
    } 
private: 
    static std::string expensive_get_name() 
    { 
     return "Name"; 
    } 
}; 
+0

這會導致所有對象具有_same_名稱,這與名稱爲「const」(即非靜態)方法的事實沒有關聯。 – MSalters

+0

是的,因爲@MSalters表示會導致所有對象實例具有相同的名稱。對'get_name()'的第一次調用會初始化該靜態變量,並將用於所有其他對該函數的調用,即使對於其他對象也是如此。 – Mark