线程安全的intrusive_ptr

时间:2016-07-22 22:16:45

标签: c++ multithreading c++11

最简单的boost::intrusive_ptr(或自制版本)如下所示:

template<typename T>
class intrusive_ptr {
public:
    intrusive_ptr(T* ptr) : ptr_(ptr)
    {
        if (ptr_) {
            intrusive_ptr_add_ref(ptr_);
        }
    }

    intrusive_ptr(const intrusive_ptr& that) : ptr_(that.ptr_)
    {
        if (ptr_) {
            intrusive_ptr_add_ref(ptr_);
        }
    }

    ~intrusive_ptr()
    {
        if (ptr_) {
            intrusive_ptr_release(ptr_);
        }
    }

    // ...

private:
    ptr_;
};

用法:

class Foo {
public:
    // ...

private:
    std::size_t refcount_;

    friend void intrusive_ptr_add_ref(const Foo* p)
    {
        ++p->refcount_;
    }

    friend void intrusive_ptr_release(const Foo* p)
    {
        if (--p->refcount_ == 0) {  // line 1
            delete p;               // line 2
        }
    }
};

intrusive_ptr<Foo> p(new Foo);

显然,Foo现已实施,intrusive_ptr<Foo>不是线程安全的。简单地将Foo::refcount_的类型更改为std::atomic<std::size_t>也不够,因为当一个线程位于第1行和第2行之间时,另一个线程可能会尝试增加引用计数。

所以我的问题是:是否有可能使intrusive_ptr线程安全,理想情况下不使用互斥体等重型机制?

2 个答案:

答案 0 :(得分:2)

  

所以我的问题是:是否有可能使intrusive_ptr线程安全,   理想情况下,不使用像互斥体这样的重型机制?

是。将计数器更改为std :: atomic就足够了,因为如果线程A将计数器值减少到零,那么可以保证没有其他的intrusive_ptr&lt;&gt;对象指向对象p。 (因为如果确实存在,则refcount值仍将大于零)。

所以你担心的竞争条件不可能发生。 (好吧,如果其他一些线程解除引用对象p的原始指针,而不是持有一个intrusive_ptr,可能会发生这种情况,但在这种情况下,所有的赌注都会被关闭,因为程序是错误的)

答案 1 :(得分:1)

您可以选择检测指针盗窃:

#include <cstdint>
#include <atomic>
#include <cassert>
#include <stdexcept>

struct allow_zero_access {};

template<typename T>
class intrusive_ptr {
public:

    intrusive_ptr(T* ptr, allow_zero_access)
    : ptr_(ptr)
    {
      assert(ptr);
      intrusive_ptr_init_ref(ptr_, allow_zero_access());
    }

    intrusive_ptr(T* ptr) : ptr_(ptr)
    {
        if (ptr_) {
            intrusive_ptr_add_ref(ptr_);
        }
    }

    intrusive_ptr(const intrusive_ptr& that) : ptr_(that.ptr_)
    {
        if (ptr_) {
            intrusive_ptr_add_ref(ptr_);
        }
    }

    intrusive_ptr& operator=(const intrusive_ptr& that)
    {
      intrusive_ptr tmp(that);
      std::swap(this->ptr_, tmp.ptr_);
      return *this;
    }

    ~intrusive_ptr()
    {
        if (ptr_) {
            intrusive_ptr_release(ptr_);
        }
    }

    // ...

private:
    T* ptr_;
};

template<class T>
struct enable_mt_intrusive_pointer
{
  private:
    friend void intrusive_ptr_init_ref(const enable_mt_intrusive_pointer* p, allow_zero_access)
    {
      assert(p);
      if (p->_refcount.fetch_add(1) != 0) {
        throw std::logic_error("stealing someone's pointer!");
      }
    }

  friend void intrusive_ptr_add_ref(const enable_mt_intrusive_pointer* p, bool first_access = false)
    {
      assert(p);
      if (p->_refcount.fetch_add(1) == 0 && !first_access) {
        throw std::logic_error("resurrecting a zombie");
      }
    }

    friend void intrusive_ptr_release(const enable_mt_intrusive_pointer* p)
    {
      assert(p);
      switch(p->_refcount.fetch_sub(1)) {
        case 1:
          delete p;
          break;
        case 0:
          throw std::logic_error("already deleted");
          break;
        default:
          ;
      }
    }

    mutable std::atomic<std::size_t> _refcount { 0 };
};

template<class T, class...Args>
intrusive_ptr<T> make_intrusive_ptr(Args&&...args)
{
  return { new T(std::forward<Args>(args)...),
           allow_zero_access() };
}



class Foo : public enable_mt_intrusive_pointer<Foo>
{
public:
    // ...

};

int main()
{
    auto p = make_intrusive_ptr<Foo>();
}

但是,实际上很少有理由在c ++程序中使用intrusive_ptr。即使在与外部c库接口时,其他侵入式指针也可以用自定义删除器封装在std :: shared_ptr中。