双重检查共享指针的锁定

Double-checked Locking for Shared Pointers

本文关键字:锁定 指针 共享 检查      更新时间:2023-10-16

免责声明:我来自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++11 std::shared_ptr ,则可以在共享指针对象上使用 atomic_loadatomic_store,它们保证彼此正确组合并使用互斥体组成;但如果使用 C++11,您也可以只使用动态初始化的 static 变量,该变量保证是线程安全的。

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

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

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

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

关于原始代码的线程安全性

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