标准库的 std::shared
智能指针在现代 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: