• Log in
Anwen  Share and Create
  • Book
  • Movies
  • Music
  • SF
  • Goodlink
  • Asks
  • Eyeopen
  • Create

标准库的 std::shared

Sharer: 阅微堂 January 8, 2020 at 11:00 pm
Link Share :https://zhiqiang.org/coding/std-shared-ptr.html - via RSS

智能指针在现代 C++里用得越多。以前只知道它大致的原理,比如使用引用计数。但很多实现细节并不清楚,最直接的,它是如何实现多线程安全的?今天找了 gnu c++ library 的实现好好看了一下。

std::shared_ptr的代码位于https://gcc.gnu.org/onlinedocs/gcc-7.5.0/libstdc++/api/a15815_source.html,它基本等价于:

template <typename T>
using std::shared_ptr = std::__shared_ptr<T>;

因此我们需要去看std::__shared_ptr<T>的实现,后者位于https://gcc.gnu.org/onlinedocs/gcc-7.5.0/libstdc++/api/a00494_source.html,几乎所有的定义都位于该文件。其继承和内存结构如下:

template<typename _Tp, _Lock_policy _Lp = __default_lock_policy>
class __shared_ptr : __shared_ptr_access<_Tp, _Lp> {
    _Tp*        _M_ptr;                 // Contained pointer.
    __shared_count<_Lp>  _M_refcount;   // Reference counter.
};

这里_Lock_policy是一个枚举量,它有三个取值:

  • _S_single:表示为单线程适用。
  • _S_mutex:表示为多线程使用,并且使用thread-layer abstractions。
  • _S_atomic:多线程使用,使用原子操作。

而__default_lock_policy在多线程程序中一般为_S_atomic(实际值依赖于编译设置,具体定义参见)。

其中基类__shared_ptr_access主要是为智能指针提供*以及->等运算符,可以忽略。

其内存布局中,第一个是指针本身,这个没什么好说的。最重要的是第二个成员__shared_count,请定义如下:

template<_Lock_policy _Lp>
class __shared_count {
    _Sp_counted_base<_Lp>*  _M_pi;
public:
    __shared_count& operator=(const __shared_count& __r) noexcept {
        _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
        if (__tmp != _M_pi) {
            if (__tmp != 0) __tmp->_M_add_ref_copy();
            if (_M_pi != 0) _M_pi->_M_release();
            _M_pi = __tmp;
        }
        return *this;
    }
};

这里面保存了一个了引用计数器的指针,也就是说std::shared_ptr实际布局中为两个指针,共使用 16 字节。

先看其基类本身的定义如下,它保存了两个引用计数:

  • _M_use_count:引用次数(std::shared_ptr指向次数)。当为 0 时,共享指针保存的元素将被删除。
  • _M_weak_count:弱引用次数(std::weak_ptr指向次数),如果引用次数大于 0 ,再加 1。当为 0 时,当前计数器将被删除。

    template<Lockpolicy _Lp = _defaultlock_policy> class Spcounted_base : public Mutexbase<_Lp> { Atomicword Muse_count; // #shared Atomicword Mweak_count; // #weak + (#shared != 0)

    public: void Mrelease() noexcept { GLIBCXXSYNCHRONIZATIONHAPPENSBEFORE(&Musecount); if (gnucxx::_exchangeandadddispatch(&Muse_count, -1) == 1) { GLIBCXXSYNCHRONIZATIONHAPPENSAFTER(&Muse_count); Mdispose();

            if (_Mutex_base<_Lp>::_S_need_barriers) {
                __atomic_thread_fence (__ATOMIC_ACQ_REL);
            }
    
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
            if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1) {
                _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
                _M_destroy();
            }
        }
    }
    
    // Called when _M_weak_count drops to zero.
    virtual void _M_destroy() noexcept { delete this; }
    
    // Called when _M_use_count drops to zero, to release the resources
    // managed by *this.
    virtual void _M_dispose() noexcept = 0;
    
    void _M_add_ref_copy() { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
    

    }

其中_M_destroy和_M_dispose都在继承类中被重新定义。不过实现中很容易看出为什么引用计数可以做到线程安全,它使用了atomic原子数据,以及很多memory barrier。

但实际引用计数指向的是继承的类,主要是_Sp_counted_deleter和_Sp_counted_ptr。

【有待完善】

作者暂无likerid, 赞赏暂由本网站代持,当作者有likerid后会全部转账给作者(我们会尽力而为)。

Tips: Until now, everytime you want to store your article, we will help you store it in Filecoin network. In the future, you can store it in Filecoin network using your own filecoin.


Support author:
Author's Filecoin address:
Or you can use Likecoin to support author:
tags:编程 C++标准库 智能指针

0 0

2012-2018 Anwen All of our posts are default licensed under CC BY 4.0 About Help Changelog Telegram
Today Quote: 2006年1月19日,新视野号宇宙飞船发射,飞向遥远的冥王星。经过三十亿英里的飞行,九年后的2015年7月14日11点49点,到达预定地点冥王星。这只比2006年的预测慢了1分钟,达到了99.99998%的准确度。 -- 《了不起的 NASA》