zoukankan      html  css  js  c++  java
  • 智能指针之share_ptr源码剖析以及线程安全测试

    shared_ptr的实现

    看了一下stl的源码,shared_ptr的实现是这样的:  shared_ptr模板类有一个__shared_count类型的成员,_M_refcount来处理引用计数的问题。__shared_count也是一个模板类,它的内部有一个指针_M_pi。所有引用同一个对象的shared_ptr都共用一个_M_pi指针。

    当一个shared_ptr拷贝复制时, _M_pi指针调用_M_add_ref_copy()函数将引用计数+1。 当shared_ptr析构时,_M_pi指针调用_M_release()函数将引用计数-1。 _M_release()函数中会判断引用计数是否为0. 如果引用计数为0, 则将shared_ptr引用的对象内存释放掉。

    __shared_count(const__shared_count& __r) : _M_pi(__r._M_pi)
    {
    if(_M_pi != 0)

    _M_pi->_M_add_ref_copy();

    COSTA_DEBUG_REFCOUNT;

    }

     这是__shared_count拷贝复制时的代码。首先将参数__r的_M_pi指针赋值给自己, 然后判断指针是否为NULL, 如果不为null 则增加引用计数。COSTA_DEBUG_REFCOUNT和COSTA_DEBUG_SHAREDPTR是

    #define COSTA_DEBUG_REFCOUNT fprintf(stdout,"%s:%d costaxu debug refcount: %d ", __FILE__,__LINE__,_M_pi->_M_get_use_count());

    #define COSTA_DEBUG_SHAREDPTR fprintf(stdout,"%s:%d costaxu debug ", __FILE__,__LINE__);

    我为了打印引用计数的调试代码,会打印文件行号和当前引用计数的值。

    __shared_count& operator=(const__shared_count& __r) // nothrow

    {

    _Sp_counted_base<_Lp>* __tmp = __r._M_pi;

    if(__tmp != _M_pi)

    {

    if(__tmp != 0)

    __tmp->_M_add_ref_copy();

    if(_M_pi != 0)

    _M_pi->_M_release();

    _M_pi = __tmp;

    }

    COSTA_DEBUG_REFCOUNT;

    return*this;

    }

    这是__share_count重载赋值操作符的代码。 首先,判断等号左右两边的__share_count是否引用同一个对象。如果引用同一个对象(__tmp==_M_pi),那么引用计数不变,什么都不用做。如果不是的话,就把等号左边的share_ptr的引用计数-1,将等号右边的引用计数+1 。例如: 有两个shared_ptr p1和p2, 运行p1= p2 。 假如p1和p2是引用同一个对象的,那么引用计数不变。 如果p1和p2是指向不同对象的,那么p1所指向对象的引用计数-1, p2指向对象的引用计数+1。

    ~__shared_count()// nothrow

    {

    if(_M_pi != 0)

    _M_pi->_M_release();

    COSTA_DEBUG_REFCOUNT;

    }

    上面是__share_count的析构函数, 其实析构函数只是调用了_M_pi的_M_release这个成员函数。_M_release这个函数,除了会将引用计数-1之外,还会判断是否引用计数为0, 如果为0就调用_M_dispose()函数。 _M_dispose函数会将share_ptr引用的对象释放内存。

    virtual void _M_dispose()// nothrow

    {

    COSTA_DEBUG_SHAREDPTR;

    _M_del(_M_ptr);

    }

    _M_del是在构造_M_pi时候就初始化好的内存回收函数, _M_ptr就是shared_ptr引用的对象指针。

    下面是我自己实现的share_ptr

    #include<iostream>
    #include<vector>
    /*
    1.auto_ptr 所有权唯一,只能有一个对象使用
    1.智能指针提前失效

    2.带标志位的智能指针 所有权不唯一 释放权唯一

    3.boost:: scope_ptr
    */
    class Ref_Management
    {
    public:
    static Ref_Management* getInstance()//引用计数类的接口,模拟__shared_count

    {
    return &rm;
    }
    private:
    Ref_Management():cursize(0){}
    Ref_Management(const Ref_Management&);
    static Ref_Management rm;
    public:
    //添加一个指针指向一块内存
    void addref(void* mptr)
    {
    if (mptr != NULL)
    {
    int index = find(mptr);
    if (index < 0)
    {
    Node tmp(mptr, 1);
    node[cursize++] = tmp;
    //node[cursize].addr = mptr;
    //node[cursize].ref = 1;
    //cursize++;
    }
    else
    {
    node[index].ref++;
    }

    //std::vector<Node>::iterator fit = find(mptr);
    //if (fit == vec.end())
    //{
    // Node node(mptr, 1);
    // vec.push_back(node);
    //}
    //else
    //{
    // (*fit).ref++;
    //}
    }
    }
    //删除一个指针
    void delref(void* mptr)
    {
    if (mptr != NULL)
    {
    int index = find(mptr);
    if (index < 0)
    {
    throw std::exception("addr is not exsit!");
    }
    else
    {
    if (node[index].ref != 0)
    {
    node[index].ref--;
    }
    }
    }
    }
    //查找指针所在的位置的引用个数
    int getref(void* mptr)
    {
    int rt = -1;
    if (mptr != NULL)
    {
    int index = find(mptr);
    if (index >= 0)
    {
    rt = node[index].ref;
    }
    }
    return rt;
    }
    private:
    //查找指针指向的位置
    int find(void* mptr)
    {
    int rt = -1;
    for (int i = 0; i < cursize; i++)
    {
    if (node[i].addr == mptr)
    {
    rt = i;
    break;
    }
    }
    return rt;
    /*std::vector<Node>::iterator it = vec.begin();
    for (it; it != vec.end(); it++)
    {
    if ((*it).addr == mptr)
    break;
    }
    return it;*/
    }
    //addr:存放的指针,ref:存放指针的位置的个数,引用计数
    class Node
    {
    public:
    Node(void* padd = NULL, int rf = 0) :addr(padd), ref(rf){}
    public:
    void* addr;
    int ref;
    };
    Node node[10];//数组的整体大小,数组中存放的是指针和指针所在位置的个数
    int cursize;//数组当前的大小
    };
    Ref_Management Ref_Management::rm;
    template<typename T>
    class Shared_Ptr
    {
    public:
    //构造函数,调用AddRef();添加内存
    Shared_Ptr(T* ptr = NULL) :mptr(ptr)
    {
    AddRef();
    }
    //拷贝构造函数 ,申请一个新的内存,引用计数为一,将数组的当前大小加一
    Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
    {
    AddRef();
    }
    //赋值运算符的重载函数,自赋值的的判断,调用~Share_ptr(),资源复制,调用AddRef();
    Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
    {
    if (this != &rhs)
    {
    this->~Shared_Ptr();
    mptr = rhs.mptr;
    AddRef();
    }
    return *this;
    }
    //析构函数,调用DelRef(),释放内存,并指向NULL;
    ~Shared_Ptr()
    {
    DelRef();
    if (GetRef() == 0)
    {
    delete mptr;
    }
    mptr = NULL;
    }
    //->运算符重载函数
    T* operator->()
    {
    return mptr;
    }
    // * 运算符重载函数
    T& operator*()
    {
    return *mptr;
    }
    private:
    //调用addref(),传入mptr,添加内存
    void AddRef()
    {
    prm->addref(mptr);
    }
    //调用delref(),传入mptr,释放内存
    void DelRef()
    {
    prm->delref(mptr);
    }
    //调用getref(),传入mptr,得到当前位置的引用计数
    int GetRef()
    {
    return prm->getref(mptr);
    }
    T* mptr;
    static Ref_Management* prm;//提供静态的接口,供本类可以调用prm对象的函数
    };
    template<typename T>
    Ref_Management* Shared_Ptr<T>::prm = Ref_Management::getInstance();

    class B;
    class A
    {
    public:
    A()
    {
    std::cout << "A()" << std::endl;
    }
    ~A()
    {
    std::cout << "~A()" << std::endl;
    }
    public:
    Shared_Ptr<B> spa;
    };
    class B
    {
    public:
    B()
    {
    std::cout << "B()" << std::endl;
    }
    ~B()
    {
    std::cout << "~B()" << std::endl;
    }
    public:
    Shared_Ptr<A> spb;
    };
    /*
    shared_ptr 相互引用
    */
    /*
    weak_ptr解决问题
    */
    int main()
    {
    Shared_Ptr<A> pa(new A());
    Shared_Ptr<B> pb(new B());
    pa->spa = pb;
    pb->spb = pa;
    return 0;
    }

    shared_ptr线程安全性问题

    关于shared_ptr的线程安全性。查了一些网上的资料,有的说是安全的,有的说不安全。引用CSDN上一篇比较老的帖子, 它是这样说的:

    “Boost 文档对于 shared_ptr 的线程安全有一段专门的记述,内容如下:

    shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)

    Any other simultaneous accesses result in undefined behavior.

    翻译为中文如下:

    shared_ptr对象提供与内置类型相同的线程安全级别。多个线程可以同时“读取”(仅使用常量操作访问)共享_ptr实例。不同的shared_ptr实例可以由多个线程同时“写入”(使用可变操作如operator=或reset来访问)(即使这些实例是副本,并且在下面共享相同的引用计数)。)

    任何其他同时进行的访问都会导致未定义的行为。

    这几句话比较繁琐,我总结一下它的意思:

    1 同一个shared_ptr被多个线程“读”是安全的。

    2 同一个shared_ptr被多个线程“写”是不安全的。

    3 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。

    如何印证上面的观点呢?

    其实第一点我觉得比较多余。因为在多个线程中读同一个对象,在正常情况下不会有什么问题。

    所以问题就是:如何写程序证明同一个shared_ptr被多个线程"写"是不安全的?

    我的思路是,在多个线程中同时对一个shared_ptr循环执行两遍swap。 shared_ptr的swap函数的作用就是和另外一个shared_ptr交换引用对象和引用计数,是写操作。执行两遍swap之后, shared_ptr引用的对象的值应该不变。

    程序如下:

    #include <stdio.h>

    #include <tr1/memory>
    #include <pthread.h>
    usingstd::tr1::shared_ptr;
    shared_ptr<int> gp(newint(2000));
    shared_ptr<int> CostaSwapSharedPtr1(shared_ptr<int> & p)
    {
    shared_ptr<int> p1(p);
    shared_ptr<int> p2(newint(1000));
    p1.swap(p2);
    p2.swap(p1);
    returnp1;
    }
    shared_ptr<int> CostaSwapSharedPtr2(shared_ptr<int> & p)
    {
    shared_ptr<int> p2(newint(1000));
    p.swap(p2);
    p2.swap(p);
    returnp;
    }
    void* thread_start(void* arg)
    {
    inti =0;
    for(;i<100000;i++)
    {
    shared_ptr<int> p= CostaSwapSharedPtr2(gp);
    if(*p!=2000)
    {
    printf("Thread error. *gp=%d ", *gp);
    break;
    }
    }
    printf("Thread quit ");
    return0;
    }
    int main()
    {
    pthread_tthread;
    intthread_num = 10, i=0;
    pthread_t* threads = newpthread_t[thread_num];
    for(;i<thread_num;i++)
    pthread_create(&threads[i], 0 , thread_start , &i);
    for(i=0;i<thread_num;i++)
    pthread_join(threads[i],0);
    delete[] threads;
    return0;
    }

     

     

    这个程序中我启了10个线程。每个线程调用10万次 CostaSwapSharedPtr2函数。 在CostaSwapSharePtr2函数中,对同一个share_ptr全局变量gp进行两次swap(写操作), 在函数返回之后检查gp的值是否被修改。如果gp值被修改,则证明多线程对同一个share_ptr执行写操作是不安全的。

    程序运行的结果如下:

     

     
    Thread error. *gp=1000
    Thread error. *gp=1000
    Thread quit

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread error. *gp=1000

    Thread quit

    Thread quit

     

     

    10个线程有9个出错。证明多线程对同一个share_ptr执行写操作是不安全的。我们在程序中,如果不运行CostaSwapSharedPtr2, 改成运行CostaSwapSharedPtr1呢?  CostaSwapSharedPtr1和CostaSwapSharedPtr2的区别在于, 它不是直接对全局变量gp进行写操作,而是将gp拷贝出来一份再进行写操作。运行的结果如下:

     

    costa@pepsi:~/test/cpp/shared_ptr$ ./b

    Thread quit

    Thread quit

    Thread quit

    Thread quit

    Thread quit

    Thread quit

    Thread quit

    Thread quit

    Thread quit

     
    Thread quit

     

    跑了很多次都没有出错。说明共享引用计数的不同的shared_ptr执行swap是线程安全的。BOOST文档是可信的。

    补充一个问题: 为什么shared_ptr可以作为STL标准容器的元素,而auto_ptr不可以    这个根据auto_ptr相信也可以找出答案了,以及auto_ptr为什么会被慢慢摒弃了;

    这篇文章小结一下:

    1 shared_ptr是一个非常实用的智能指针。

    2 shared_ptr的实现机制是在拷贝构造时使用同一份引用计数。

    3 对同一个shared_ptr的写操作不是线程安全的。 对使用同一份引用计数的不同shared_ptr是线程安全的。

    线程安全测试取自:http://my.oschina.net/costaxu/blog/103119

  • 相关阅读:
    ZooKeeper概述(转)
    ZooKeeper典型应用场景(转)
    部署与管理ZooKeeper(转)
    Hbase分布式安装部署过程
    HBase安装
    使用info命令查看Redis信息和状态
    java定时调度器解决方案分类及特性介绍
    谈谈应用层切面设计
    七层协议和四层协议
    HTTP协议详解
  • 原文地址:https://www.cnblogs.com/xcb-1024day/p/11332230.html
Copyright © 2011-2022 走看看