简体   繁体   English

以下 C ++ 代码中实现的 DCL(双重检查锁定)是否是线程安全的?

[英]Is the DCL(double-checked locking) implemented in the following C ++ code thread-safe?

Here is a piece of code that is DCL (double-checked locking) implemented by 'acquire-release' semantics in C++.这是一段由 C++ 中的“获取-释放”语义实现的 DCL(双重检查锁定)代码。 The code is as follows:代码如下:

std :: atomic <Singleton *> Singleton :: m_instance;
std :: mutex Singleton :: m_mutex;

Singleton * Singleton :: getInstance () {
    Singleton * tmp = m_instance.load (std :: memory_order_acquire); // 3
    if (tmp == nullptr) {
        std :: lock_guard <std :: mutex> lock (m_mutex);
        tmp = m_instance.load (std :: memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton; // 1
            m_instance.store (tmp, std :: memory_order_release); // 2
        }
    }
    return tmp;
}

On https://en.cppreference.com/w/cpp/atomic/memory_order , the interpretation of memory_order_release is: A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store.https://en.cppreference.com/w/cpp/atomic/memory_order上,memory_order_release 的解释是:使用此 memory 命令的存储操作执行释放操作:在此之后不能重新排序当前线程中的读取或写入店铺。 All writes in the current thread are visible in other threads that acquire the same atomic variable.当前线程中的所有写入在获取相同原子变量的其他线程中都是可见的。

My understanding is: load-store, store-store can not be reordered, but did not say that other-store can not be reordered.我的理解是:load-store,store-store不能重排,但没说other-store不能重排。

So I think: '1' includes not only read and write instructions, but also call instruction, then the call instruction may be reordered behind '2';所以我认为:'1'不仅包括读写指令,还包括调用指令,那么调用指令可能在'2'后面重新排序; then '3' may get an unsafe 'tmp' pointer.那么'3'可能会得到一个不安全的'tmp'指针。

Let me describe the above paragraph again:让我再描述一下上面的段落:

Disassemble ‘1’ into the following two possible pseudo-instructions:
tmp = allocate ();
call Singleton constructor (tmp); // 4

I think '4' may be reordered after '2'.我认为“4”可能会在“2”之后重新排序。 After one thread executes '2', then another thread completes '3' and obtains the tmp pointer.在一个线程执行'2'后,另一个线程完成'3'并获得tmp指针。 At this time, the tmp pointer is an unsafe Singleton pointer.此时tmp指针是一个不安全的Singleton指针。

So I have this question: Is the above code thread-safe?所以我有这个问题:上面的代码是线程安全的吗?

Yes, it is safe!是的,它是安全的!

If the acquire-load returns null (ie, the singleton is not yet initialized) you acquire the mutex.如果获取加载返回 null(即 singleton 尚未初始化),则您获取互斥锁。 Inside the mutex the reload can be relaxed since the modifications of m_instance is protected by the mutex anyway, ie, if some other thread has already initialized the singleton, then the mutex-release of that thread must have happend before our mutex-acquire operation, so it is guaranteed that we see the updated m_instance .在互斥体内部,重新加载可以放松,因为m_instance的修改无论如何都受到互斥体的保护,即,如果某个其他线程已经初始化了 singleton,那么该线程的互斥释放必须在我们的互斥获取操作之前发生,所以可以保证我们看到更新的m_instance

If the acquire-load (1) "sees" the value written by the release-store (2), the two operations synchronize-with each other thereby establishing a happens-before relation, so you can safely access the object tmp points to.如果 acquire-load (1) “看到” release-store (2) 写入的值,这两个操作会相互同步,从而建立起之前发生的关系,因此您可以安全地访问 object tmp 指向的值。

Update更新
The release-store is also protected by the mutex, and it is not possible that a part of the initialization of tmp is reordered with the store. release-store 也受互斥体保护,tmp 的初始化部分不可能与 store 重新排序。 In general one should avoid to argue about possible reorderings.一般来说,应该避免争论可能的重新排序。 The standard says nothing about if/how operations can be reordered.该标准没有说明是否/如何重新排序操作。 Instead, it defines the (inter-thread)-happens-before relation.相反,它定义了(线程间)-happens-before 关系。 Any reorderings that a compiler may perform are merely a result of applying the rules of the happens-before relations.编译器可能执行的任何重新排序仅仅是应用发生前关系规则的结果。

If the acquire-load (1) loads the value written by the release-store (2), the two operations synchronize-with each other thereby establishing a happens-before relation, ie, (2) happens-before (3).如果acquire-load (1) 加载release-store (2) 写入的值,则这两个操作相互同步,从而建立happens-before 关系,即(2)happens-before (3)。 But since (1) is sequenced-before (2) and the happens-before relation is transitive, it has to be guaranteed that (1) happens-before (3).但是由于 (1) 是先于 (2) 排序的,并且发生在之前的关系是可传递的,因此必须保证 (1) 发生在 (3) 之前。 Thus it is not possible to reorder (1) (or parts of it) with (2).因此,不可能用 (2) 重新排序 (1) (或它的一部分)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM