【问题标题】:How to use double checked lock to init a shared_ptr如何使用双重检查锁来初始化 shared_ptr
【发布时间】:2012-09-22 18:32:30
【问题描述】:

(假设 VC++ 2010:(1)可以使用 /volatile:ms,(2)还没有 std::atomic,(3)没有线程安全的静态变量初始化,(4)没有 std::call_once)

如果我有一个普通的 C 指针,我可以实现以下双重检查锁定模式以避免每次锁定的成本:

static volatile void * ptr = nullptr;

//...
if ( ptr == nullptr)
{
   // Acquire Lock
   if (ptr == nullptr)
   {
      // some code
      // ptr = ...; // init ptr
   }
   // Release Lock
}
// ....

从 VC++ 2005 开始,volatile 确保上面的代码是正确的。假设我可以接受代码不可移植。

现在假设我需要用 std::shared_ptr 或 boost::shared_ptr 替换普通指针,我将如何做同样的事情?如何使 shared_ptr 不稳定?我需要另一个 volatile 标志吗?

【问题讨论】:

  • 不,它没有。双重检查锁定是错误的。
  • 是什么让您相信 volatile 使该代码正确?
  • 如果您将static volatile void *ptr = nullptr; 更改为std::atomic<void*> ptr = nullptr;,则双重检查锁定将起作用。这假设 C++11。
  • @Daniel : volatile 保证了 VC++ 2005+ 中的内存屏障。因此“自 VC++ 2005 起,volatile 确保上述代码正确。”;-]
  • 是的,我确实假设 /volatile:ms 使用 VC++。 (因此,代码在标准 C++ 中是不可移植的,甚至是不正确的)。抱歉没有说清楚。我的问题应该是如何对 VS2010 中的 std::shared_ptr 或 boost::shared_ptr 做同样的事情(其中 std::atomic 尚不可用。)

标签: c++ c++11 locking shared-ptr lazy-initialization


【解决方案1】:

在 C++11 中,shared_ptr 有原子访问器函数。要编写使用shared_ptr 的双重检查锁,请使用这些访问器:

static std::shared_ptr<MyType> ptr;
if (std::atomic_load(ptr) == 0) {
    // lock the lock
    if (std::atomic_load(ptr) == 0) {
        std::shared_ptr<MyType> local_ptr(new MyType);
        std::atomic_store(ptr, local_ptr);
    }
    // unlock the lock
}
return ptr;

【讨论】:

  • std::atomic 将是理想的。不幸的是,我需要使用 VS2010。
【解决方案2】:

从 VC++ 2005 开始,volatile 确保上面的代码是正确的。

不,它没有。 volatile 与线程或原子性无关。

您当前的代码不正确,任何 C++ 标准都不能保证产生合理的行为。

由于您的假装锁定代码在一般情况下不起作用,因此它肯定不适用于shared_ptr 或其他智能指针。如果您想要更便宜的锁定,请查看无锁编码模式。

【讨论】:

  • 在 OP 特别提到的 Visual C++ 中,volatile 保证了完整的内存围栏。如果问题是关于标准 C++ 的,那么您是完全正确的,但我不认为这就是问题所在。
  • 是的。我应该清楚它是关于 VC++ 特定代码(使用 /volatile:ms)。因此上面的代码是正确的。
【解决方案3】:

在 C++ 2011 中,甚至不需要使用 any 显式同步。根据 6.7 [stmt.dcl] 第 4 段,初始化由系统同步:

如果控制在变量初始化的同时进入声明,并发执行将等待初始化完成。

这似乎暗示std::shared_ptr&lt;T&gt;可以这样初始化:

{
    static std::shared_ptr<MyType> ptr(new MyType(/*...*/));
    // ...
}

【讨论】:

  • 谢谢。但是,AFAIK,这个功能甚至在 VC2012 中都没有(如果我错了,请纠正我)。另外,(1)它是否使用延迟初始化? (2)如果我需要在调用ctor之前在“//一些代码”部分有一些昂贵的逻辑,那仍然不理想。
  • 这是延迟初始化,无论如何您都需要在某些时候进行昂贵的初始化。如果初始化不适合构造函数,您可以调用返回 std::shared_ptr&lt;MyType&gt; 的函数。实现将应用std::call_once() 之类的东西,以确保线程在初始化期间被正确阻塞,并且在之后的阻碍最小。我不知道是否有任何编译器实现了这个功能。我仍然认为指出它是如何正确完成的很有用。
  • @LostInTranslation :你说得对:VC++ 2012 - 根据this blog post N2660 没有实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
相关资源
最近更新 更多