4

免责声明:我来自 Java 背景,因此,我不知道 C++(和相关库)的许多内部结构是如何工作的。

我已经阅读了足够多的内容,知道双重检查锁定是邪恶的,单例模式的正确和安全实现需要适当的工具。

我相信以下代码可能不安全,受编译器重新排序和未初始化对象分配的影响,但我不确定我是否遗漏了一些我不了解该语言的内容。

typedef boost::shared_ptr<A> APtr;

APtr g_a;
boost::mutex g_a_mutex;
const APtr& A::instance()
{
  if (!g_a)
  {
    boost::mutex::scoped_lock lock(g_a_mutex);
    if (!g_a)
    {
      g_a = boost::make_shared<A>();
    }
  }

  return g_a;
}

我相信实际代码是在 C++03 下编译的。

这个实现不安全吗?如果是这样,怎么做?

4

2 回答 2

2

是的,双重检查锁定模式在古代语言中是不安全的。我没有比这个修复双重检查锁定有什么问题中的解释更好的了?

问题显然是行分配实例——编译器可以自由地分配对象,然后分配指针给它,或者将指针设置到它将被分配的位置,然后分配它。

如果您使用的是 C++11 std::shared_ptr,则可以在共享指针对象上使用atomic_loadandatomic_store,这可以保证彼此正确组合并使用互斥锁;但是如果你使用 C++11,你也可以只使用一个动态初始化的static变量,它保证是线程安全的。

于 2016-03-17T14:59:02.107 回答
1

所有这些在现代 C++ 中都是绝对不必要的。对于除了恐龙之外的任何人来说,单例代码都应该像这样简单:

A& A::instance() {
    static A a;
    return a;
}

在 C++11 及更高版本中 100% 线程安全。

然而,我有一个强制性的声明:单例是邪恶的反模式,应该永远被禁止。

关于原代码的线程安全

是的,提供的代码是不安全的。g_a 由于读取是在互斥锁之外完成的,因此对自身的修改完全有可能 是可见的,但对对象的修改g_a所指向的却不是。结果,线程将绕过互斥锁并返回指向非构造对象的指针。

于 2016-03-17T14:48:12.487 回答