1

このスレッドは安全ですか:

class X;

class Once {
 public:
  Once(X* x) : x_(x) {}

  X* Get() {
    if (!x_) return NULL;
    // all dirty reads end up here.

    // This could be any type of scoped lock...
    some_scoped_lock lock(m);

    // if (!x_) return x_;  // omitted because it's a no op

    X* ret(x_);  // might get NULL if we waited for lock
    x_ = NULL;   // idempotent

    return ret;
  }

 private:
  X *x_;
  some_kind_of_mutex m;

  // Boilerplate to make all other constructors and default function private
  ....
}

(編集: c++11 と古いバージョンの両方に興味があります)

私が理解しているように、ダブルチェック ロックの問題は、一部のメモリ モデルでは、保護された var への書き込みが発生し、早期に表示される可能性があることです。新しい値の有効性の前提条件がないため、上記のコードにはこの問題はないと思います。これが正しいコードであるための唯一の要件は、ロック下のすべての読み取りが、コンストラクターでの書き込みとロック下の書き込みに関してクリーンでなければならないことだと思います。


更新: OK、これは「未定義の動作」の落とし穴を引き起こし、そのため、銀行口座を空にするなど、合法的に何でもできるようです。とはいえ、誤動作する場合はありますか?

4

2 に答える 2

2

C ++ 11標準によると、データの競合があるため、動作は未定義です。詳細:スレッドAはx_その行に書き込みx_ = NULL、スレッドBは。x_の行から読み取りますif (!x_) return NULL。これらの2つの操作は順序付けられていません。これは、データの競合があることを意味し、未定義の動作を意味します。

データの競合を避けるために、アトミック型を使用する必要があります。これはあなたの例では非常に簡単です。しかし、質問はもっと一般的だったと思います。それにもかかわらず:

struct Once
{
    std::atomic<X*> _x;
    explicit Once(X* x) : _x{x} {}
    X* Get()
    {
        return _x.exchange(nullptr, std::memory_order::memory_order_relaxed);
    }
};
于 2012-04-20T17:04:43.013 に答える
1

正確にはわかりませんが、コンストラクターにメモリバリア/ロックを挿入する必要があると思います。

割り当てられるメモリがコンストラクターx_の前の値を持っていて、NULLこの値が一部のスレッドで古くなっていると想像してください。このスレッドが呼び出しを試みると想像してください。メモリ バリアなしで早期に返されるため、Get()誤って返されます。NULL

于 2012-04-20T16:55:44.467 に答える