Half Built Singleton

Double-checked locking, with a real mutex and the second check present:

static Registry* g_instance = nullptr;
static std::mutex g_mutex;

Registry* get() {
    if (g_instance == nullptr) {                    // 1: fast path, no lock
        std::lock_guard<std::mutex> lk(g_mutex);
        if (g_instance == nullptr) {                // 2: re-check under the lock
            g_instance = new Registry();            // 3
        }
    }
    return g_instance;                              // 4
}

The mutex is correct, the second check is correct, and on x86 every test passes. Callers still occasionally receive a Registry whose constructor has not finished. Which statement identifies the defect?

A

Two threads can both fail the check on line 1, both take the lock, and both run line 3, so two Registry objects are constructed.

B

Line 3 publishes in two parts, the constructor's writes and then the pointer store, and line 1 reads the pointer with no synchronization, so a second thread can see a non-null pointer whose object is still being built.

C

The second check on line 2 is redundant once the lock is held, and the redundant read is what races.

D

g_instance needs to be volatile, otherwise the compiler caches the pointer in a register across the lock acquisition.