在以下单例“获取”功能中,其他线程是否可以将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
中没有太多意义,这纯粹是为了学习。
答案 0 :(得分:2)
您的代码显示了双重检查锁定模式(DCLP)的有效实现。
同步由std::mutex
或std::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
之前,其他线程也观察到相同的顺序。