【问题标题】:C++11 Singleton. Static variable is thread safe? Why?C++11 单例。静态变量是线程安全的吗?为什么?
【发布时间】:2015-12-24 20:03:25
【问题描述】:

我刚刚读到,这个结构:

const bg::AppSettings& bg::AppSettings::GetInstance()
{
    static AppSettings instance;
    return instance;
}

是创建单例的线程安全且有效的方法吗?!我是否正确,每次调用此方法时静态 AppSettings 变量都是相同的?!我对这个范围的界定有点困惑......

我的正常方法是使用 unique_ptr 作为我班级的静态成员......但这似乎有效......有人可以向我解释一下,这是怎么回事?!

顺便说一句:const 在这里有意义吗?!

【问题讨论】:

  • 是的。它是线程安全的。另外,请注意,实例将在第一次调用 GetInstance() 时被初始化,而不是在程序启动时。在某些情况下,这可能是不受欢迎的行为。
  • 所以如果我是正确的:“实例”是在函数第一次被调用时创建的,不需要任何互斥锁。如果是这样,那么这太简单了 x_X 编辑:啊,你更快安德烈 :) 我猜对于一个从文件中读取设置的简单 AppSettings 类,没关系......我希望......
  • 因为标准保证了它。你有足够的理由吗?
  • @Isaac,不是真的,正如 Howard Hinnat 所说:Visual Studio 直到 VS-2015 才实现 C++11 的这一方面。

标签: c++11


【解决方案1】:

在 C++11(及更高版本)中,函数 local static AppSettings 的构造保证是线程安全的。注意:Visual Studio 直到 VS-2015 才实现 C++11 的这一方面。

编译器将在AppSettings 旁边放置一个隐藏标志,指示它是否是:

  • 未构建。
  • 正在建设中。
  • 已构建。

第一个线程将找到设置为“未构造”的标志并尝试构造对象。成功构建后,标志将设置为“已构建”。如果另一个线程出现并发现标志设置为“正在构建”,它将等待标志设置为“正在构建”。

如果构造因异常而失败,标志将设置为“未构造”,并且将在下一次通过时重试构造(在同一线程或不同线程上)。

对象instance 将在程序的其余部分保持构造,直到main() 返回,此时instance 将被破坏。

每次执行线程通过AppSettings::GetInstance(),都会引用完全相同的对象。

在 C++98/03 中,不能保证构造是线程安全的。

如果AppSettings的构造函数递归进入AppSettings::GetInstance(),则行为未定义。

如果编译器可以“在编译时”看到如何构造instance,则允许。

如果AppSettings 具有constexpr 构造函数(用于构造instance 的构造函数),并且instanceconstexpr 限定,则编译器需要在编译时构造instance。如果instance在编译时被构造,“not-constructed/constructed”标志将被优化掉。

【讨论】:

  • 但是如果一个线程看到“未构造”,然后在将标志设置为“正在构造”之前发生任务切换怎么办?另一个线程可以看到“未构造”并开始构造,那么如果在构造完成之前发生任务切换回第一个线程,它不会覆盖对象吗?是什么防止这种情况发生?
  • @RemyLebeau:标志本身的设置/读取是线程安全的。这可以通过各种特定于实现的方式来实现,例如原子、双重检查锁定等。标准要求它工作,但没有指定如何工作。对于每个硬件平台,它的实现通常略有不同。这是一个流行的 ABI 实现接口:mentorembedded.github.io/cxx-abi/abi.html#once-ctor
  • 非常感谢。这很好地解释了这种行为! :) 也许值得检查生成的汇编代码。
  • @DoubleVoid:哦,绝对!毫不犹豫地检查生成的汇编程序!那终极的答案。而且没有足够的程序员打扰。向那个方向前进的荣誉! :-)
【解决方案2】:

您的代码的行为与此类似:

namespace {
    std::atomic_flag initialized = ATOMIC_FLAG_INIT;
    std::experimental::optional<bg::AppSettings> optional_instance;
}

const bg::AppSettings& bg::AppSettings::GetInstance()
{
    if (!initialized.test_and_set()) {
        optional_instance.emplace();
    }
    return *optional_instance;
}

通过在程序的整个过程中存在一个线程安全标志,编译器可以在每次调用函数时检查这个标志,并且只初始化你的变量一次。不过,真正的实现可以使用其他机制来获得同样的效果。

【讨论】:

  • 不知道experimental::optional ...谢谢你的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-05
  • 1970-01-01
  • 2011-12-27
  • 2010-10-01
相关资源
最近更新 更多