2017-06-13 21 views
2

は、私は以下のクラスがありますため、高価な機能であるget_object_nameの事実に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_name()が初めて呼び出されたときにそれを呼び出すしたいと思います。もしそれが決して呼び出されなければ、私は名前をつかむ無駄な資源を使いません。

私は_name.empty()への最初の呼び出しを心配しています。現在のコードはスレッドセーフであることが保証されているのですか、ロックを関数の先頭に移動する必要がありますか? empty()への呼び出しは、スレッドセーフであると信じて私をリード

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

私はこのスライドが立ち上がっthis one特にハーブサッターの会談、のいくつかを見ました。しかし、私の変数(_name)はmutableです。その "const ==スレッドセーフ"ルールはここでも適用されますか?

get_name()は、_nameを変更できる唯一の機能です。

+2

そのパターンの名前があります:[ダブルチェックロック](https://en.wikipedia.org/は、 wiki/Double-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が初期化され、ラムダが終了するまで同時呼び出しがブロックされます。
同期は完全に処理されます。つまり、stringへの参照を取得したスレッドは、スレッドから安全に読み取ることができます。

+0

それは私が恐れていたものです。あなたの 'call_once'ソリューションはかなり涼しいですが、私はそれが好きです。私はそれが小さなオーバヘッドを持っていると想定しています(ループ内にスローするとプログラムの処理速度が遅くなることはありません)。 – Mark

+0

@マーク '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

関連する問題