源码探析
根据 glibc 源码绘制 UML 如下图所示:
其中,_Sp_counted_base
的两个计数成员定义为:
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
接下来阅读 _Sp_counted_base::_M_release()
的源码:
template <_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
// ...
void _M_release() noexcept
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
// __exchange_and_add_dispatch() 返回“比较/交换”操作之前的内存值。
// 当强引用计数降为 0 之后,再也不可能上升,因为强引用计数是通过 shared_ptr 复制构造增加的。
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose(); // 释放资源
// There must be a memory barrier between dispose() and destroy()
// to ensure that the effects of dispose() are observed in the
// thread that runs destroy().
// See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence(__ATOMIC_ACQ_REL);
}
// 当强引用计数降为 0,应该对弱引用计数减 1.
// Be race-detector-friendly. For more info see bits/c++config.
_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(); // delete this,释放引用计数本身
}
}
}
// ...
};
解释:
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER
// Macros for race detectors.
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(A) and
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(A) should be used to explain
// atomic (lock-free) synchronization to race detectors:
// the race detector will infer a happens-before arc from the former to the
// latter when they share the same argument pointer.
//
// The most frequent use case for these macros (and the only case in the
// current implementation of the library) is atomic reference counting:
// void _M_remove_reference()
// {
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&this->_M_refcount);
// if (__gnu_cxx::__exchange_and_add_dispatch(&this->_M_refcount, -1) <= 0)
// {
// _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&this->_M_refcount);
// _M_destroy(__a);
// }
// }
// The annotations in this example tell the race detector that all memory
// accesses occurred when the refcount was positive do not race with
// memory accesses which occurred after the refcount became zero.
#ifndef _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE
# define _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(A)
#endif
#ifndef _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER
# define _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(A)
#endif
以上可以看出,通过对原子变量的“happens-before”设置,可以线程安全地检查引用计数降为 0,然后可以毫无顾及地释放内存:
- 当强引用计数降为 0,则可以释放无锁地资源内存。
- 当弱引用计数和强引用计数都降为 0,则可以无锁地释放引用计数的内存。所以,引用计数的加减、引用计数内存释放、资源释放都是线程安全的。
那么,如果修改 shared_ptr 管理的资源,是线程安全的吗?
答:不是。从源码来看,如果直接访问资源的内容,没有任何线程同步措施。
结论
shared_ptr 的引用计数是线程安全的。
shared_ptr 管理的资源可以线程安全地释放。
shared_ptr 只保证能够线程安全地管理资源的生命期,不保证其资源可以线程安全地被访问。
有没有 ABA 问题?
原子变量的 CAS 存在 ABA 问题,那么这里存在 ABA 问题吗?如果存在,那么会影响最终结果吗?
答:这里不存在 ABA 问题。因为当引用计数降为 0,之后就不再可能有其他的 shared_ptr 对象持有资源了,此时最后一个 shared_ptr ——也就是当前的 shared_ptr 对象—— 析构之前,可以进行内存的安全释放,并且不需要持锁。