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 的写入并在早期变得可见。我认为上面的代码没有这个问题,因为新值的有效性没有前提条件。我认为这是正确代码的唯一要求是,对于构造函数中的写入和锁定下的写入而言,锁定下的所有读取都必须是干净的。


更新:好的,这似乎引发了“未定义行为”的陷阱,因此可以合法地做任何事情,包括耗尽你的银行账户。也就是说,是否存在行为不端的情况?

4

2 回答 2

2

根据 C++11 标准,行为是未定义的,因为存在数据竞争。x_更详细地说:线程 A在行中写入,线程 B在行中x_ = NULL读取。这两个操作没有先后顺序。这意味着存在数据竞争,这意味着未定义的行为。x_if (!x_) return NULL

您应该使用原子类型来避免数据竞争。在您的示例中,这非常简单。但是,我认为这个问题更笼统。尽管如此:

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 回答