在关于并行编程的讲座中,我们被告知不应再使用 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 单例。
但我不确定为什么不能保证这些步骤的顺序:我认为 C++ 和 Java 使用顺序一致性内存模型,它不允许任何类型的 StoreLoad/LoadStore/StoreStore/LoadLoad 重新排序。即使使用 Total Store Order,允许 StoreLoad 重新排序,为什么 2 和 3 可以交换?
最佳答案
更新:我认为这适用:
来自:https://en.cppreference.com/w/cpp/language/eval_order
...8) The side effect (modification of the left argument) of the built-in assignment operator and of all built-in compound assignment operators is sequenced after the value computation (but not the side effects) of both left and right arguments, and is sequenced before the value computation of the assignment expression (that is, before returning the reference to the modified object)..."
如果“副作用”包含调用构造函数;然后编译器可以在调用构造函数之前将原始内存的地址存储在 m_instance
中。然后第二个线程会认为 m_instance
已完全构建。
自 C++11 以来,以下是线程安全的:
class A
{
private:
A() {};
public:
A& get_instance()
{
static A instance;
return instance;
}
};
Variables declared at block scope with the specifier static have static storage duration but are initialized the first time control passes through their declaration...
If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once...
来自:https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
关于c++ - C++ 中的内存模型和单例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57738586/