当前位置:编程学习 > 网站相关 >>

shared_ptr线程安全性分析

正如《STL源码剖析》所讲,“源码之前,了无秘密”。本文基于shared_ptr的源代码,提取了shared_ptr的类图和对象图,然后分析了shared_ptr如何保证文档所宣称的线程安全性。本文的分析基于boost 1.52版本,编译器是VC 2010。

shared_ptr的线程安全性

boost官方文档对shared_ptr线程安全性的正式表述是:shared_ptr对象提供与内置类型相同级别的线程安全性。【shared_ptrobjects offer the same level of thread safety as built-in types.】具体是以下三点。

1. 同一个shared_ptr对象可以被多线程同时读取。【A shared_ptrinstance can be "read" (accessed using only const operations)simultaneously by multiple threads.】

2. 不同的shared_ptr对象可以被多线程同时修改(即使这些shared_ptr对象管理着同一个对象的指针)。【Different shared_ptr instances can be "written to"(accessed using mutable operations such as operator= or reset) simultaneouslyby multiple threads (even when these instances are copies, and share the samereference count underneath.) 】

3. 任何其他并发访问的结果都是无定义的。【Any other simultaneous accesses result in undefined behavior.】

第一种情况是对对象的并发读,自然是线程安全的。

第二种情况下,如果两个shared_ptr对象A和B管理的是不同对象的指针,则这两个对象完全不相关,支持并发写也容易理解。但如果A和B管理的是同一个对象P的指针,则A和B需要维护一块共享的内存区域,该区域记录P指针当前的引用计数。对A和B的并发写必然涉及对该引用计数内存区的并发修改,这需要boost做额外的工作,也是本文分析的重点。

另外weak_ptr和shared_ptr紧密相关,用户可以从weak_ptr构造出shared_ptr,也可以从shared_ptr构造weak_ptr,但是weak_ptr不涉及到对象的生命周期。由于shared_ptr的线程安全性是和weak_ptr耦合在一起的,本文的分析也涉及到weak_ptr。

下面先从总体上看一下shared_ptr和weak_ptr的实现。

shared_ptr的结构图

以下是从boost源码提取出的shared_ptr和weak_ptr的类图。

\

我们首先忽略虚线框内的weak_ptr部分。最高层的shared_ptr就是用户直接使用的类,它提供shared_ptr的构造、复制、重置(reset函数)、解引用、比较、隐式转换为bool等功能。它包含一个指向被管理对象的指针,用来实现解引用操作,并且组合了一个shared_count对象,用来操作引用计数。

但shared_count类还不是引用计数类,它只是包含了一个指向引用计数类sp_counted_base的指针,功能上是对sp_counted_base操作的封装。shared_count对象的创建、复制和删除等操作,包含着对sp_counted_base的增加和减小引用计数的操作。

最后sp_counted_base类才保存了引用计数,并且对引用计数字段提供无锁保护。它也包含了一个指向被管理对象的指针,是用来删除被管理的对象的。sp_counted_base有三个派生类,分别处理用户指定Deleter和Allocator的情况:

1. sp_counted_impl_p:用户没有指定Deleter和Allocator

2. sp_counted_impl_pd:用户指定了Deleter,没有指定Allocator

3. sp_counted_impl_pda:用户指定了Deleter和 Allocator

创建指针P的第一个shared_ptr对象的时候,子对象shared_count同时被建立, shared_count根据用户提供的参数选择创建一个特定的sp_counted_base派生类对象X。之后创建的所有管理P的shared_ptr对象都指向了这个独一无二的X。

然后再看虚线框内的weak_ptr就清楚了。weak_ptr和shared_ptr基本上类似,只不过weak_ptr包含的是weak_count子对象,但weak_count和shared_count也都指向了sp_counted_base。

如果上面的文字还不够清楚,下面的代码就能说明问题。

shared_ptr<SomeObject> SP1(new SomeObject());

shared_ptr<SomeObject> SP2=SP1;

weak_ptr<SomeObject> WP1=SP1;

执行完以上代码后,内存中会创建以下对象实例,其中红色箭头表示指向引用计数对象的指针,黑色箭头表示指向被管理对象的指针。

\

 

从上面可以清楚的看出,SP1、SP2和WP1指向了同一个sp_counted_impl_p对象,这个sp_counted_impl_p对象保存引用计数,是SP1、SP2和WP1等三个对象共同操作的内存区。多线程并发修改SP1、SP2和WP1,有且只有sp_counted_impl_p对象会被并发修改,因此sp_counted_impl_p的线程安全性是shared_ptr以及weak_ptr线程安全性的关键问题。而sp_counted_impl_p的线程安全性是在其基类sp_counted_base中实现的。下面将着重分析sp_counted_base的代码。

引用计数类sp_counted_base

幸运的是,sp_counted_base的代码量很小,下面全文列出来,并添加有注释



class sp_counted_base
 
{
 
private:
 
     // 禁止复制
 
    sp_counted_base( sp_counted_base const & );
 
    sp_counted_base & operator= ( sp_counted_baseconst & );
 
 
 
     // shared_ptr的数量
 
    long use_count_;   
 
     // weak_ptr的数量+1
 
    long weak_count_;       
 
 
 
public:
 
     // 唯一的一个构造函数,注意这里把两个计数都置为1
 
    sp_counted_base(): use_count_( 1 ), weak_count_( 1 ){    }
 
 
 
     // 虚基类,因此可以作为基类
 
    virtual ~sp_counted_base(){    }
 
 
 
     // 子类需要重载,用operator delete或者Deleter删除被管理的对象
 
    virtual void dispose() = 0; 
 
 
 
     // 子类可以重载,用Allocator等删除当前对象
 
    virtual void destroy(){
 
        delete this;
 
    }
 
 
 
    virtual void * get_deleter( sp_typeinfo const & ti ) = 0;
 
 
 
     // 这个函数在根据shared_count复制shared_count的时候用到
 
     // 既然存在一个shared_count作为源,记为A,则只要A不释放,
 
     // use_count_就不会被另一个线程release()为1。
 
     // 另外,如果一个线程把A作为复制源,另一个线程释放A,执行结果是未定义的。
 
     void add_ref_copy(){
 
        _InterlockedIncrement( &use_count_ );
 
    }
 
 
 
     // 这个函数在根据weak_count构造shared_count的时候用到
 
     // 这是为了避免通过weak_count增加引用计数的时候,
 
     // 另外的线程却调用了release函数,清零use_count_并释放了指向的对象
 
    bool add_ref_lock(){
 
        for( ;; )
 
        {
 
            long tmp = static_cast< long const volatile& >( use_count_ );
 
            if( tmp == 0 ) return false;
 
 
 
            if( _InterlockedCompareExchange( &use_count_, tmp + 1, tmp ) == tmp )return true;
 
        }
 
    }
 
 
 
    void rel
补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,