线程安全入侵_ptr

Thread-safe intrusive_ptr

本文关键字:ptr 入侵 安全 线程      更新时间:2023-10-16

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线程安全,理想情况下不使用像互斥这样的重机制?

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

是的。将计数器更改为std::atomic就足够了,因为如果线程A将计数器值减为零,则可以保证没有其他入侵_ptr<>对象指向对象p。(因为如果确实存在,refcount值仍将大于零)。

所以你担心的比赛状况是不可能发生的。(好吧,如果其他线程取消引用指向对象p的原始指针,而不是持有intrusive_ptr,则可能会发生这种情况,但在这种情况下,由于程序有缺陷,所有赌注都会被取消)

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

#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库接口时,其他侵入性指针也可以封装在带有自定义deleter的std::shared_ptr中。