Double-checked locking around value going to null?

廉价感情. 提交于 2020-01-16 00:54:14

问题


Is this thread safe:

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
  ....
}

(edit: i'm interested in both c++11 and older versions)

As I understand it the problem with Double-checked locking is that in some memory models the write to the guarded var can occur and become visible to early. I think the above code doesn't have this issue because there is no precondition for the validity of the new value. I think the only requirement for this to be correct code is that all reads under the lock must be clean with respect to writes in the constructor and writes under the lock.


Update: OK, it seems that this invokes the "undefined behavior" pitfall and therefor can legally do anything, including drain your bank account. That said, are there any cases where it will misbehave?


回答1:


According to the C++11 standard, the behavior is undefined, because there is a data race. In more detail: Thread A writes to x_ in the line x_ = NULL and Thread B reads from x_ in the line with if (!x_) return NULL. These two operations are not sequenced. That means there is a data race, and that implies undefined behavior.

You should use an atomic type to avoid the data race. This is very simple in your example. However, I think the question was more general. Nevertheless:

struct Once
{
    std::atomic<X*> _x;
    explicit Once(X* x) : _x{x} {}
    X* Get()
    {
        return _x.exchange(nullptr, std::memory_order::memory_order_relaxed);
    }
};



回答2:


Not exactly sure, but I think you have to insert a memory barrier/lock in the constructor.

Imagine that the memory which will be allocated for x_ had the value of NULL before constructor, and this value is stale on some thread. Imagine that this thread tries to call Get() -- it will erroneously get NULL back due to the early return w/o memory barriers.



来源:https://stackoverflow.com/questions/10250043/double-checked-locking-around-value-going-to-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!