在一次关于并行编程的讲座中,我们被告知在C++中不应该再使用这种旧的单例线程安全模式:
class A {
public:
static A* instance() {
if (!m_instance) {
std::lock_guard<std::mutex> guard(m_instance_mutex);
if (!m_instance)
m_instance = new A();
}
return m_instance;
}
private:
A()
static A* m_instance;
static std::mutex m_instance_mutex;
}这是因为在没有干净的内存模型的情况下,无法保证以下步骤没有明确的顺序: 1.为A分配内存2.初始化对象A 3.使m_instance指向该内存
例如,可能会在3之后从2重新排序: m_instance可能已经指向那里,但没有有效的对象。另一个线程现在可以看到一个非零指针,但对无效数据进行操作。
这就是为什么我们应该使用Meyer的Singleton来放置内存栅栏。
但我不确定为什么这些步骤的顺序没有保证:我认为C++和Java利用了顺序一致性内存模型,该模型不允许任何类型的StoreLoad/LoadStore/StoreStore/LoadLoad重新排序。即使是允许StoreLoad重新排序的总商店顺序,为什么2和3可以互换?
发布于 2019-08-31 22:29:16
更新:我认为这适用于:
来自:https://en.cppreference.com/w/cpp/language/eval_order
...8)内置赋值运算符和所有内置复合赋值运算符的副作用(左参数的修改)在左参数和右参数的值计算(但不是副作用)之后排序,并在赋值表达式的值计算之前排序(即,在返回对修改的对象的引用之前)...“
如果“副作用”包括调用构造函数,则编译器可以在调用构造函数之前将原始内存的地址存储在m_instance中。然后第二个线程会认为m_instance已经完全构造好了。
下面是自C++11以来的线程安全特性:
class A
{
private:
A() {};
public:
A& get_instance()
{
static A instance;
return instance;
}
};在块作用域中使用说明符
声明的静态变量具有静态存储持续时间,但在控件第一次通过其声明时被初始化...
如果多个线程试图同时初始化同一个静态局部变量,则初始化只发生一次...
来自:https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
发布于 2019-09-20 18:36:21
只是猜测,可以使用像atomic_store这样的原子操作来确保步骤2和3的顺序吗?step1可能不需要让m_instance初始化为NULL。
https://stackoverflow.com/questions/57738586
复制相似问题