简体   繁体   中英

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

So I think: '1' includes not only read and write instructions, but also call instruction, then the call instruction may be reordered behind '2'; then '3' may get an unsafe 'tmp' pointer.

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'. After one thread executes '2', then another thread completes '3' and obtains the tmp pointer. At this time, the tmp pointer is an unsafe Singleton pointer.

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

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.

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. 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. 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). But since (1) is sequenced-before (2) and the happens-before relation is transitive, it has to be guaranteed that (1) happens-before (3). Thus it is not possible to reorder (1) (or parts of it) with (2).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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