Cpp || shared_ptr 模拟实现(线程安全相关)

shared_ptr模拟实现

  • 代码实现
#include<iostream>
#include<thread>
#include<mutex>

using namespace std; 


template<class T>
class Shared_Ptr{
  public:
    Shared_Ptr(T* ptr=nullptr)
      :_ptr(ptr)
       ,_UseCount(new int(1))
       ,_mut(new mutex)
  {

  }

    ~Shared_Ptr(){
      Release();
    }

    Shared_Ptr(const Shared_Ptr<T>& sp)
      :_ptr(sp._ptr)
       ,_UseCount(sp._UseCount)
       ,_mut(sp._mut)
  {
    Add_UseCount();
  }

    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){
      if(_ptr!=sp._ptr){
        //释放管理的旧的资源
        Release();
        //共享管理对象的资源,并增加引用计数
        _ptr=sp._ptr;
        _UseCount=sp._UseCount;
        _mut=sp._mut;

        Add_UseCount();
      }
      return *this;
    }

    T& operator*(){
      return *_ptr;
    }

    T* operator->(){
      return _ptr;
    }
    void Add_UseCount(){
      //加锁或者使用加1的原子操作
      _mut->lock();
      ++(*_UseCount);
      _mut->unlock();
    }
 
    int UseCount(){
      return *_UseCount;
    }

  private:
    void Release(){
      //用于控制引用计数
     bool deleteFalg=false;

     _mut->lock();

     if(--(*_UseCount)==0){
       delete _ptr;
       delete _UseCount;
       deleteFalg=true;
     }
     _mut->unlock();

     if(deleteFalg==true){
       delete _mut;
     }
    }
  private:
    T* _ptr;
    int* _UseCount;
    mutex* _mut;
};

int main(){
  Shared_Ptr<int> sp(new int(10));
  Shared_Ptr<int> sp1(sp);
  *sp1=20;
  cout<<"sp.UseCount():"<<sp.UseCount()<<endl;
  cout<<"sp1.UseCount():"<<sp1.UseCount()<<endl;
 
  Shared_Ptr<int> sp2(new int(10));
  sp1=sp2;
  cout<<"sp.UseCount():"<<sp.UseCount()<<endl;
  cout<<"sp1.UseCount():"<<sp1.UseCount()<<endl;
  cout<<"sp2.UseCount():"<<sp2.UseCount()<<endl;

  sp=sp2;

  cout<<"sp.UseCount():"<<sp.UseCount()<<endl;
  cout<<"sp1.UseCount():"<<sp1.UseCount()<<endl;
  cout<<"sp2.UseCount():"<<sp2.UseCount()<<endl;

  return 0;
}
  • 运行结果

在这里插入图片描述

std::shared_ptr 的线程安全问题

  • std::shared_ptr的线程安全问题
  • 通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分为两方面:
  • 1.智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
  • 2.智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
线程安全版本的shared_ptr使用
#include<iostream>
#include<thread>
#include<mutex>

using namespace std;

mutex mux;

struct Date{
  int _year=1;
  int _month=1;
  int _day=1;

  ~Date(){
    cout<<"~Date()"<<endl;
  }
};
template<class T>
class Shared_Ptr{

  private:
    T* _Ptr;
    int *_UseCount;
    mutex *_Mux;

  public:
    Shared_Ptr(T* ptr)
      :_Ptr(ptr)
       ,_UseCount(new int(1))
       ,_Mux(new mutex)
  {

  }

    Shared_Ptr(const Shared_Ptr<T>& sp)
      :_Ptr(sp._Ptr)
       ,_UseCount(sp._UseCount)
       ,_Mux(sp._Mux)
  {
    addRef();
  }

    int addRef(){
      _Mux->lock();
      ++(*_UseCount);
      _Mux->unlock();
      return *_UseCount;
    }

    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){
      if(_Ptr!=sp._Ptr){
        if(subRef()==0){
          delete _Ptr;
          delete _UseCount;
          delete _Mux;
          _Ptr=nullptr;
          _UseCount=nullptr;
          _Mux=nullptr;
        }
        _Ptr=sp._Ptr;
        _UseCount=sp._UseCount;
        _Mux=sp._Mux;
      }
      return *this;
    }
    int subRef(){
      _Mux->lock();
      --(*_UseCount);
      _Mux->unlock();
      return *_UseCount;
    }

    ~Shared_Ptr(){
      if(subRef()==0){
        delete _Ptr;
        delete _UseCount;
        delete _Mux;
        _Mux=nullptr;
        _UseCount=nullptr;
        _Ptr=nullptr;
      }
    }

    T& operator*(){
      return *_Ptr;
    }

    T* operator->(){
      return _Ptr;
    }

    int UseCount(){
      return *_UseCount;
    }
};


Shared_Ptr<Date> sp(new Date);

void Test(){

  for(size_t i=0;i<1000;i++){
    Shared_Ptr<Date> p(sp);
    mux.lock();
    
    p->_year++;
    p->_month++;
    p->_day++;
    mux.unlock();
  }

}


int main(){

  thread t1(Test);
  thread t2(Test);

  t1.join();
  t2.join();
  
   cout<<sp->_year<<endl;
   cout<<sp->_month<<endl;
   cout<<sp->_day<<endl;
  return 0;
}
  • 运行结果

在这里插入图片描述

线程不安全版本
#include<iostream>
#include<thread>
#include<mutex>

using namespace std;

mutex mux;

struct Date{
  int _year=1;
  int _month=1;
  int _day=1;

  ~Date(){
    cout<<"~Date()"<<endl;
  }
};
template<class T>
class Shared_Ptr{

  private:
    T* _Ptr;
    int *_UseCount;
    mutex *_Mux;

  public:
    Shared_Ptr(T* ptr)
      :_Ptr(ptr)
       ,_UseCount(new int(1))
       ,_Mux(new mutex)
  {

  }

    Shared_Ptr(const Shared_Ptr<T>& sp)
      :_Ptr(sp._Ptr)
       ,_UseCount(sp._UseCount)
       ,_Mux(sp._Mux)
  {
    addRef();
  }

    int addRef(){
      _Mux->lock();
      ++(*_UseCount);
      _Mux->unlock();
      return *_UseCount;
    }

    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){
      if(_Ptr!=sp._Ptr){
        if(subRef()==0){
          delete _Ptr;
          delete _UseCount;
          delete _Mux;
          _Ptr=nullptr;
          _UseCount=nullptr;
          _Mux=nullptr;
        }
        _Ptr=sp._Ptr;
        _UseCount=sp._UseCount;
        _Mux=sp._Mux;
      }
      return *this;
    }
    int subRef(){
      _Mux->lock();
      --(*_UseCount);
      _Mux->unlock();
      return *_UseCount;
    }

    ~Shared_Ptr(){
      if(subRef()==0){
        delete _Ptr;
        delete _UseCount;
        delete _Mux;
        _Mux=nullptr;
        _UseCount=nullptr;
        _Ptr=nullptr;
      }
    }

    T& operator*(){
      return *_Ptr;
    }

    T* operator->(){
      return _Ptr;
    }

    int UseCount(){
      return *_UseCount;
    }
};


Shared_Ptr<Date> sp(new Date);

void Test(){

  for(size_t i=0;i<1000;i++){
    Shared_Ptr<Date> p(sp);
    //mux.lock();
    
    p->_year++;
    p->_month++;
    p->_day++;
    //mux.unlock();
  }

}


int main(){

  thread t1(Test);
  thread t2(Test);

  t1.join();
  t2.join();
  
   cout<<sp->_year<<endl;
   cout<<sp->_month<<endl;
   cout<<sp->_day<<endl;
  return 0;
}
  • 运行结果

在这里插入图片描述

可以看到去掉安全锁之后,每次的运行结果都是不同的.这就是两个线程抢占式执行的结果

std::shared_ptr 的循环引用

struct ListNode
{
 int _data;
 shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
  }
  • 循环引用分析

  • 1.node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。

  • 2.node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

  • 3.node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。

  • 4.也就是说_next析构了,node2就释放了。

  • 5.也就是说_prev析构了,node1就释放了。

  • 6.但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放

在这里插入图片描述

  • 解决方案
// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
struct ListNode
{
 int _data;
 weak_ptr<ListNode> _prev;
 weak_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl;} 
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0; 
 }
  • 如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题.
  • 仿函数的删除器
// 仿函数的删除器
template<class T>
struct FreeFunc {
 void operator()(T* ptr)
 {
 cout << "free:" << ptr << endl;
 free(ptr);
 }
};
template<class T>
struct DeleteArrayFunc {
 void operator()(T* ptr)
 { 
 cout << "delete[]" << ptr << endl;
 delete[] ptr; 
 }
};
int main()
{
 FreeFunc<int> freeFunc;
 shared_ptr<int> sp1((int*)malloc(4), freeFunc);
 DeleteArrayFunc<int> deleteArrayFunc;
 shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
 
 return 0; 
}

C++11和boost中智能指针的关系

  • 1.C++ 98 中产生了第一个智能指针auto_ptr.
  • 2.C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  • 3.C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  • 4.C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的
    scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

RAII扩展学习

  • RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题。
  • 守卫锁:利用RAII思想解决线程死锁,构造函数中加锁,析构函数中解锁,防止死锁的形成和资源的管理
#include <thread>
#include <mutex>
// C++11的库中也有一个lock_guard,下面的LockGuard造轮子其实就是为了学习他的原理
template<class Mutex>
class LockGuard
{
public:
 LockGuard(Mutex& mtx)
 :_mutex(mtx)
 {
 _mutex.lock();
 }
 ~LockGuard()
 {
 _mutex.unlock();
 }
 LockGuard(const LockGuard<Mutex>&) = delete;
private:
 // 注意这里必须使用引用,否则锁的就不是一个互斥量对象
 Mutex& _mutex;
};
mutex mtx;
static int n = 0;
void Func()
{
 for (size_t i = 0; i < 1000000; ++i)
 {
 LockGuard<mutex> lock(mtx);
 ++n;
 }
}

int main()
{
 int begin = clock();
 thread t1(Func);
 thread t2(Func);
 t1.join();
 t2.join();
 int end = clock();
 cout << n << endl;
 cout <<"cost time:" <<end - begin << endl;
 
 return 0; 
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值