现在的位置: 首页 > 综合 > 正文

shared_ptr:线程安全、循环引用 shared_ptr:线程安全、循环引用

2017年11月03日 ⁄ 综合 ⁄ 共 4506字 ⁄ 字号 评论关闭
 

shared_ptr:线程安全、循环引用

分类: Linux多线程编程 288人阅读 评论(0) 收藏 举报

   shared_ptr用来管理堆对象可以避免delete,但是注意shared_ptr本身是个对象因此其的声明周期、shared_ptr对象的读写操作非原子化那么在多线程环境下仍然存在很多问题,即shared_ptr对象本身的线程安全级别非100%。在多线程中访问同一个shared_ptr对象应该用mutex保护。

   shared_ptr使用不当会延长锁管理的对象(假设为A)的生命周期,即有什么地方不小心留下一个shared_ptr的拷贝,那么对象A生命期无疑被延长了,因为对象计数不为0将不会被销毁。boost::bind会将函数参数拷贝一份,那么shared_ptr<T> one, boost::function<void()> fp=boost::bind(&fun,one)//这种情况会下one管理的对象生命期一定晚于fp对象。

   shared_ptr可能会导致循环引用即:对象A持有一个关于B的shared_ptr,B持有一个关于A的shared_ptr,那么A、B离开作用域的时候它们的计数都不为0(因为对方持有一个shared_ptr指向自己)而谁也不肯先释放这个shared_ptr,最后造成内存泄露。解决方法是:A持有关于B的shared_ptr,B持有关于A的weak_ptr。

    当一个管理对象A的shared_ptr离开作用域且计数变为0时那么对象A将在这个线程析构(这个线程可能不是创建A的线程),若析构比较费事,那么析构A的线程将被拖累,解决办法是:找个专门的线程管理所有的shared_ptr对象,并执行析构操作。

   我曾犯过的一个错误:若shared_ptr管理对象A,假设对象里面有new的堆内存,在对象A销毁前(即shared_ptr计数大于0)的时候,在某个地方A通过某些操作释放了A里里面的堆内存....那么最后的结果是?不错,double free,在A存活的时候释放了A里面的部分内存,那么在shared_ptr计数为0要销毁A的时候又去释放那块内存,然后就double free了。所以记住:避免shared_ptr管理的对象直接的内存管理操作以免造成重复释放

下面是多线程共享一个shared_ptr对象时的保护措施:

  1. #include<iostream>  
  2. #include<string>  
  3. #include<unistd.h>  
  4. #include<pthread.h>  
  5. #include<boost/shared_ptr.hpp>  
  6. #include<boost/weak_ptr.hpp>  
  7. using namespace std;  
  8. using namespace boost;  
  9. pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//保护globalPtr的互斥量  
  10. class test{//shared_ptr管理的对象(假设为A)  
  11.     public:  
  12.         void show(string s){  
  13.             cout<<"test::show()  in: "<<s<<endl;  
  14.         }  
  15. };  
  16. shared_ptr<test> globalPtr(new test);  
  17.   
  18. void fun(const shared_ptr<test>& ptr,string s){//用于展示多层函数传递shared_ptr时只用最外层函数持有shared_ptr对象实体,内层函数可以用const reference传递,毕竟大多数都是操作shared_ptr管理的对象而不是shared_ptr本身,const reference不会遇见返回拷贝shared_ptr对象导致的性能问题  
  19.     ptr->show(s);//操作shared_ptr管理的对象  
  20. }  
  21. void read(){//读globalPtr时候使用mutex保护  
  22.     shared_ptr<test> localptr;//减小临界区  
  23.     {  
  24.         pthread_mutex_lock(&mutex);  
  25.         localptr=globalPtr;//以后的操作只与localptr有关  
  26.         pthread_mutex_unlock(&mutex);  
  27.     }  
  28.     fun(localptr,"read()");  
  29. }  
  30. void write(){//重写globalPtr时使用mutex保护  
  31.     shared_ptr<test> newptr(new test);//重写globalPtr  
  32.     shared_ptr<test> lockptr;//###1  
  33.     {  
  34.         pthread_mutex_lock(&mutex);  
  35.         //globalPtr=newptr;//###2//这里与###1的区别是:直接使用本语句将有可能出现:globalPtr管理的对象A计数为1即这里将newptr赋给globalPtr后对象A将会被析构,则会使临界区变长,那么解决方式就是按照###1的那几句话  
  36.         lockptr.swap(globalPtr);//###1  
  37.         globalPtr=newptr;//###1  
  38.         pthread_mutex_unlock(&mutex);  
  39.     }  
  40.     fun(newptr,"write()");  
  41.     //fun(shared_ptr<test> one(new test),g()//构造一个临时的shared_ptr作为函数参数,函数参数求值顺序不一定,可能:new test第一,g()第二但是如果g()抛出一个异常,那么永远不可能到达shared_ptr的构造函数  
  42. }  
  43. void reset(){//销毁shared_ptr对象  
  44.     shared_ptr<test> localptr;//同样为了缩小临界区  
  45.     {  
  46.         pthread_mutex_lock(&mutex);  
  47.         localptr.swap(globalPtr);//globalPtr轻松就置空了且不会引起所管理的对象A在这里被析构,临界区非常小  
  48.         pthread_mutex_unlock(&mutex);  
  49.     }  
  50.     fun(localptr,"reset()");  
  51. }  
  52. void* worker(void* arg){  
  53.     int* x=(int*)arg;  
  54.     switch(*x){  
  55.         case 1:  
  56.             read();  
  57.             break;  
  58.         case 2:  
  59.             write();  
  60.             break;  
  61.         case 3:  
  62.             sleep(1);//pid3线程为了让其它两个线程执行完,不然这里使globalPtr置空了其它线程会报错  
  63.             reset();  
  64.             break;  
  65.         default:  
  66.             cout<<"are you kidding me"<<endl;  
  67.     }  
  68. }  
  69. int main(){  
  70.     pthread_t pid1,pid2,pid3;  
  71.     int i=1;  
  72.     pthread_create(&pid1,NULL,worker,&i);  
  73.     int j=2;//本来想用i++的但是所有线程都执行i=3的情形这说明:在前面两个线程还没有提取i=1,i=2的时候,线程3已经将i置为3了那么前两个线程就提取了i=3,这个错误以前犯过...哎...  
  74.     pthread_create(&pid2,NULL,worker,&j);  
  75.     int k=3;  
  76.     pthread_create(&pid3,NULL,&worker,&k);  
  77.     pthread_join(pid1,NULL);  
  78.     pthread_join(pid2,NULL);  
  79.     pthread_join(pid3,NULL);  
  80.     return 0;  
  81. }  

输出结果:

test::show()  in: write()test::show()  in: read()

test::show()  in: reset()

shared_ptr循环引用的例子:内存泄露问题

  1. #include<iostream>  
  2. #include<boost/shared_ptr.hpp>  
  3. #include<boost/weak_ptr.hpp>  
  4. using namespace std;  
  5. using namespace boost;  
  6. class B;  
  7. class A{  
  8.     public:  
  9.         shared_ptr<B> ptr_A;  
  10.         ~A(){  
  11.             cout<<"~A()"<<endl;  
  12.         }  
  13. };  
  14. class B{  
  15.     public:  
  16.         //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  
  17.         weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  
  18.         ~B(){  
  19.             cout<<"~B()"<<endl;  
  20.         }  
  21. };  
  22. int main(){  
  23.     shared_ptr<A> a(new A);  
  24.     shared_ptr<B> b(new B);  
  25.     a->ptr_A=b;  
  26.     b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...  
  27.     return 0;  
  28. }  

 
     若是循环引用则什么输出都没有即对象没有析构,内存泄露了。

     若是B持有A的weak_ptr则输出:

~A()
~B()

抱歉!评论已关闭.