这样的单例实现可以对两个商店重新排序吗?

时间:2018-07-01 20:22:37

标签: c++ c++14 atomic memory-barriers

在以下单例“获取”功能中,其他线程是否可以将instance视为非空,但是almost_done仍然为假? (假设almost_done最初是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;
}

如果可以,为什么?有什么道理?

我知道没有人能“脱离”获取发布部分,但是2不能输入并用1重新排序吗?

我知道对于C ++中的线程安全单例来说,我不需要这种复杂的技术,是的,almost_done中没有太多意义,这纯粹是为了学习。

1 个答案:

答案 0 :(得分:2)

您的代码显示了双重检查锁定模式(DCLP)的有效实现。
同步由std::mutexstd::atomic::instance处理,具体取决于线程输入代码的顺序。

  

其他线程是否可以将实例视为非空,但是几乎_done仍然为假?

否,这是不可能的。

DCLP模式可确保在开始时执行加载获取(返回非空值)的所有线程都保证看到instance指向有效内存,而almost_done==true 因为负载已与商店发布同步。

一个可能的原因是,在一个很小的机会窗口中,第一个线程(#1)持有std::mutex,而第二个线程(#2)进入第一个{{1 }}-声明。

在#2锁定if之前,它可能会观察到std::mutex的值(由于互斥锁对此负责,但仍指向未同步的内存)。
但是即使发生这种情况(此模式下的一种有效情况),由于发布围栏(由#1调用)命令将商店放松到instance,因此#2也会看到almost_done==true 在存储松弛到almost_done之前,其他线程也观察到相同的顺序。