C++ shared_ptr的线程安全性

源码探析

根据 glibc 源码绘制 UML 如下图所示:
shared_ptr.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 问题的说明可参考 文章1文章2
  • 所谓 ABA 问题的本质是:虽然“比较/交换”是一个原子操作,但是基于这个“比较/交换”的结果来决定进行的其他操作,此时就不再是原子的了,可能是危险的。

答:这里不存在 ABA 问题。因为当引用计数降为 0,之后就不再可能有其他的 shared_ptr 对象持有资源了,此时最后一个 shared_ptr ——也就是当前的 shared_ptr 对象—— 析构之前,可以进行内存的安全释放,并且不需要持锁。

参考资料

参考1
参考2
《Linux多线程服务端编程》,陈硕。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值