shared_ptr的线程安全性

    shared_ptr的出现在某种程度上解放了c++程序员,c++11标准原生的支持了并发编程,在并发编程中shared_ptr的线程安全问题如何保证呢?先撇开shared_ptr对象的线程安全性,先看shared_ptr本身的线程安全问题。

    我们知道,shared_ptr的底层实现原理是引用计数,关于这个计数是否线程安全呢,如果我们把shared_ptr分别传递到不同的线程中,是否会在成引用计数的竞争问题。我们来看shared_ptr引用计数的底层实现。shared_ptr继承了下面的模板类,用它来管理引用计数。其中有两个变量一个表示shared_ptr的引用数,另外一个表示weak_ptr的引用数,我们知道weak_ptr不会增加只能指针的引用数也就是说不持有对象,他的使用必须通过lock方法获取它指向的shared_ptr才能使用。

template<_Lock_policy _Lp = __default_lock_policy>
   class _Sp_counted_base
   : public _Mutex_base<_Lp>
   {
   public:  
     _Sp_counted_base() noexcept
     : _M_use_count(1), _M_weak_count(1) { }
     
     virtual
     ~_Sp_counted_base() noexcept
     { }
 
     //当_M_use_count为0时调用,是个纯虚函数(必须实现),这个函数的作用是释放指针指向的对象所持有的资源,即*this
     virtual void
     _M_dispose() noexcept = 0;
     
     // 当_M_weak_count为0时调用,释放自己本身的资源,即this
     //  _M_weak_count = _M_weak_count + (_M_use_count!= 0),当_M_weak_count和_M_use_count都为0时释放this
     virtual void
     _M_destroy() noexcept
     { delete this; }
     
     virtual void*
     _M_get_deleter(const std::type_info&) noexcept = 0;

    //增加一个引用
     void
     _M_add_ref_copy()
     { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
 
     void
     _M_add_ref_lock();

     bool
     _M_add_ref_lock_nothrow();

     void
     _M_release() noexcept
     {
       _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
  //首先use_count减去1,并对比减操作之前的值,如果减之前是1,说明减后是0,a1没有任何shared_ptr指针指向它了将销毁对象
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
  {
           _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
    _M_dispose();
    //如果destory和dispose存在内存屏障,保证dispose函数的效果在destory函数的调用该线程的可见性
    if (_Mutex_base<_Lp>::_S_need_barriers)
      {
   __atomic_thread_fence (__ATOMIC_ACQ_REL);
      }

    //同时对a1的weak_count减去1,也对比减操作之前的值,如果减之前是1,说明减后是0,a1没有weak_ptr指向它了,
    //应该将管理对象销毁,于是调用_M_destroy()销毁了管理对象
           _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();
             }
  }
     }
 
     void
     _M_weak_add_ref() noexcept
     { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }

     void
     _M_weak_release() noexcept
     {
       _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);
    if (_Mutex_base<_Lp>::_S_need_barriers)
      {
   __atomic_thread_fence (__ATOMIC_ACQ_REL);
      }
    _M_destroy();
  }
     }
 
    //获取引用计数 
     long
     _M_get_use_count() const noexcept
     {
       return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
     }

   private:  
     _Sp_counted_base(_Sp_counted_base const&) = delete;
     _Sp_counted_base& operator=(_Sp_counted_base const&) = delete;

     _Atomic_word  _M_use_count;   
     _Atomic_word  _M_weak_count;   
   };

    可能有一些人看过boost中智能指针引用计数的实现,不过好像是通过锁来实现的。这里我们可以看到跟boost的是有所不同的,这里的智能指针的引用计数在手段上使用了atomic原子操作,只要在shared_ptr在拷贝或赋值时增加引用,析构时减少引用就可以了。

    首先源自操作是线程安全的,所有智能指针在多线程下引用计数也是安全的,也就是说智能指针在多线程下传递使用时引用计数是不会有线程安全问题的,但是这能真正的保证shared_ptr指针的线程安全问题吗。

     虽然通过原子操作解决了引用计数的计数的线程安全问题, 但是智能指针指向的对象的线程安全问题,智能指针没有做任何的保证。  首先智能指针有两个变量,一个是指向的对象的指针,还有一个就是我们上面看到的引用计数管理对象, 当智能指针发生拷贝的时候,标准哭的实现是县拷贝智能指针,再拷贝引用计数对象(拷贝引用计数对象的时候,会使use_count加一),这两个操作并不是原子的,隐患就出现在这里,引用一下陈硕老师的例子:地址点击打开链接

          

     

       这里陈硕老师说道:“这正是多线程读写同一个shared_ptr必须枷锁的原因”, 为了保证程序的绝对的安全是没错的, 但也不是绝对,上面的情景是特殊场景,这种场景也只是为了说明问题而已,真正开发过程中不一定会用到此场景。其实这个问题的根本还是上面说的智能指针指向的对象的线程安全,shared_ptr没有做任何保证,上面的情景就打破了这一准则,在赋值的过程中,改变了shard_ptr指向的对象的内容,甚至不只是修改了对象这么简单,上面的情景直接把智能指针指向的对象给换了。这中情况不用想肯定会出问题。如果你能保证不会有多个线程同时修改或替换指针指向的对象,不用加锁是完全没有问题的,或者说指针指向的对象本身已经是线程安全(包括多线程下的读写安全和构造析构安全)。总之一句话智能指针指向的对象的线程安全,标准库是没有保证的。 

   扩展知识: 1 点击打开链接

                       2 点击打开链接

                       3 点击打开链接 

  • 12
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shared_ptr是C++11引入的一种智能指针,用于管理动态内存的释放,可以避免内存泄漏和野指针问题。在多线程环境中,使用shared_ptr需要注意其线程安全shared_ptr线程安全主要体现在两个方面: 1. 引用计数的线程安全shared_ptr内部通过引用计数来管理对象的生命周期,当一个shared_ptr对象被创建或复制时,它的引用计数会增加,当该对象被销毁或赋值给其他对象时,引用计数会减少。多线程环境中,多个线程同时访问同一个shared_ptr对象时可能引发竞争条件。为了保证引用计数的线程安全shared_ptr使用了原子操作来保证引用计数的增加和减少的原子,从而避免竞争条件。 2. 共享资源的线程安全shared_ptr在构造时接受一个指针作为参数,指向需要管理的资源。在多线程环境中,如果多个线程同时访问同一个资源,可能会引发竞争条件。为了保证共享资源的线程安全,需要在访问共享资源时使用适当的同步机制,例如使用互斥锁来确保同时只有一个线程可以访问共享资源。 总结来说,shared_ptr线程安全主要依靠引用计数的原子操作保证引用计数的线程安全,同时需要开发者在访问共享资源时使用适当的同步机制来保证共享资源的线程安全。虽然shared_ptr本身是线程安全的,但在使用时仍需要开发者小心地处理多线程访问共享资源的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值