Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can two stores be reordered in such singleton implementation?

In the following singleton 'get' function, can other threads see instance as not-null, but almost_done still false? (Say almost_done is initially false.)

Singleton *Singleton::Get() {
    auto tmp = instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> guard(lock);
        tmp = instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton();
            almost_done.store(true, std::memory_order_relaxed); // 1
            std::atomic_thread_fence(std::memory_order_release);
            instance.store(tmp, std::memory_order_relaxed); // 2
        }
    }
    return tmp;
}

If they can, why? What's the rationale?

I know nothing can "get out" of an acquire-release section, but can't 2 enter it and be reordered with 1?

I'm aware I don't need such complex techniques for thread-safe singletons in C++, and yes, there's not much sense in almost_done, this is purely for learning.

like image 445
ledonter Avatar asked May 08 '26 19:05

ledonter


1 Answers

Your code shows a valid implementation of the Double-Checked-Locking pattern (DCLP).
Synchronization is handled by either the std::mutex or std::atomic::instance depending on the order in which threads enter the code.

can other threads see instance as not-null, but almost_done still false?

No, this is not possible.

The DCLP pattern guarantees that all threads that perform a load-acquire (that returns a non-null value) at the beginning, are guaranteed to see instance point at valid memory and almost_done==true because the load has synchronized with the store-release.

A reason one might think it is possible, is in the small window of opportunity where the first thread (#1) is holding the std::mutex while a second thread (#2) is entering the first if-statement.

Before #2 locks the std::mutex, it may observe a value for instance (still pointing at unsynchronized memory because the mutex is responsible for that, but hasn't synchronized yet).
But even if that happens (a valid scenario in this pattern), #2 will see almost_done==true since the release fence (called by #1) orders the store-relaxed to almost_done before the store-relaxed to instance and that same order is observed by other threads.

like image 164
LWimsey Avatar answered May 11 '26 09:05

LWimsey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!