共享指针的双重检查锁定

Double-checked Locking for Shared Pointers

免责声明: 我来自 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 下编译的。

这个实现不安全吗?如果可以,怎么做?

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

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

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

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

关于原代码的线程安全

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

是的,双重检查锁定模式在古老的语言中是不安全的。我不能比 What's wrong with this fix for double checked locking?:

中的解释做得更好

The problem apparently is the line assigning instance -- the compiler is free to allocate the object and then assign the pointer to it, OR to set the pointer to where it will be allocated, then allocate it.

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