这是一个正确的C ++ 11双重检查锁定版本与shared_ptr?

时间:2015-06-18 09:41:23

标签: c++ multithreading c++11

Jeff Preshing的这个article声明双重检查锁定模式(DCLP)在C ++ 11中是固定的。用于此模式的经典示例是单例模式,但我碰巧有不同的用例,我仍缺乏处理经验" atomic<>武器" - 也许这边的人可以帮助我。

以下一段代码是否是杰夫在"Using C++11 Sequentially Consistent Atomics"下所描述的正确的DCLP实现?

class Foo {
    std::shared_ptr<B> data;
    std::mutex mutex;

    void detach()
    {
      if (data.use_count() > 1)
      {
        std::lock_guard<std::mutex> lock{mutex};
        if (data.use_count() > 1)
        {
          data = std::make_shared<B>(*data);
        }
      }
    }

public:
  // public interface
};

3 个答案:

答案 0 :(得分:2)

不,这不是DCLP的正确实现。

问题是外部检查 data.use_count() > 1 访问对象(类型B with reference count),可以删除(未引用)互斥保护部分。任何类型的记忆围栏都无法帮助。

为什么 data.use_count()访问对象

假设已执行这些操作:

shared_ptr<B> data1 = make_shared<B>(...);
shared_ptr<B> data = data1;

然后您有以下布局(此处未显示weak_ptr支持):

         data1             [allocated with B::new()]         data
                           --------------------------
 [pointer type] ref; -->   |atomic<int> m_use_count;|    <-- [pointer type] ref
                           |B obj;                  |
                           --------------------------

每个shared_ptr对象只是一个指针,指向已分配的内存区域。该内存区域嵌入了B类型的对象加上原子计数器,反映了指向给定对象的shared_ptr的数量。当此计数器变为零时,释放内存区域(并且B对象被销毁)。确切地说,此计数器由shared_ptr::use_count()返回。

UPDATE :执行,可以导致访问已经释放的内存(最初,两个shared_ptr指向同一个对象,.use_count()是2):

/* Thread 1 */                   /* Thread 2 */       /* Thread 3 */
Enter detach()                   Enter detach()
Found `data.use_count()` > 1     
Enter critical section                                   
Found `data.use_count()` > 1
                                 Dereference `data`,
                                 found old object.
Unreference old `data`,
`use_count` becomes 1 
                                                      Delete other shared_ptr,
                                                      old object is deleted
Assign new object to `data`
                                 Access old object
                                 (for check `use_count`)
                                 !! But object is freed !!

外部检查应该只使用指向对象的指针来决定是否需要获取锁定。

顺便说一句,即使你的实施也是正确的,它有一点意义:

  1. 如果可以同时从多个线程访问data(和detach),对象的唯一性没有任何优势,因为它可以从几个线程访问。如果要更改对象,则对data的所有访问都应受外部互斥锁的保护,在这种情况下,detach()不能同时执行。

  2. 如果data(和detach)只能由同一个线程访问,detach实现根本不需要任何锁定。< / p>

答案 1 :(得分:1)

如果两个线程同时在同一detach实例上调用Foo,则构成数据争用,因为std::shared_ptr<B>::use_count()(只读操作)将与{{1}同时运行移动赋值运算符(一种修改操作),它是一个数据争用,因此是未定义行为的原因。如果std::shared_ptr<B>实例永远不会同时访问,另一方面,没有数据竞争,但是Foo在您的示例中将是无用的。问题是:std::mutex的指针如何首先被分享?如果没有这一重要信息,即使从不同时使用data,也很难判断代码是否安全。

答案 2 :(得分:0)

根据您的消息来源,我认为您仍需要在第一次测试之前和第二次测试之后添加线程围栏。

std::shared_ptr<B> data;
std::mutex mutex;

void detach()
{
  std::atomic_thread_fence(std::memory_order_acquire);
  if (data.use_count() > 1)
  {
    auto lock = std::lock_guard<std::mutex>{mutex};
    if (data.use_count() > 1)
    {
      std::atomic_thread_fence(std::memory_order_release);
      data = std::make_shared<B>(*data);
    }
  }
}