智能指针在现代 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
(实际值依赖于编译设置,具体定义参见https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.4/a01123.html)。
其中基类__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<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base : public _Mutex_base<_Lp> {
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
public:
void _M_release() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1) {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
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
。
【有待完善】
Q. E. D.