shared_ptr应用细节

shared_ptr应用细节

boost与C++11中shared_ptr对数组支持的区别

boost中的shared_ptr支持数组而C++ 11中的shared_ptr还不支持,要在C++17中才有对数组支持。boost 从1.53版本起添加了对数组的支持。如下代码的测试环境是VS2015 x64

#include "boost/smart_ptr.hpp"
//#include <memory>
#define _crtdbg_map_alloc 
int main()
{
    {
        boost::shared_ptr<int[1024]> p(new  int[1024]);
        //std::shared_ptr<int[1024]> p(new int[1024]);
    }

    _CrtDumpMemoryLeaks();
}

shared_ptr< void > 万能指针

不管是std中的还是boost中的,shared_ptr< void >可以用于任何类型的动态对象,在析构时都可以正确delete 该类型对象。参考如下资料
Why do std::shared_ptr< void > work
boost 1.66 shared_ptr文档

shared_ptr线程安全

boost 中的和STL中的对线程安全性要求不一样
- boost shared_ptr的线程安全
一个shared_ptr实体可以被多个线程同时读。不同shared_ptr实体可以多个线程同时写。如果要从多个线程读写同一个shared_ptr对象,那么需要加锁。
- STL shared_ptr的线程安全

All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur;

照意思理解,对同一个对象分享所有权的shared_ptr在多个线程上是可以进行读写操作而不需要加锁。官网上也一个例子说明这种用法。(这里有点不太懂,不知道怎么去验证)

  • 在vs2015 x64跑如下代码
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>

struct Base
{
    Base() { std::cout << "  Base::Base()\n"; }
    // Note: non-virtual destructor is OK here
    ~Base() { std::cout << "  Base::~Base()\n"; }
};

struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};

void thr(std::shared_ptr<Base> p)
{
    std::shared_ptr<Base> lp = p; // thread-safe, even though the
                                  // shared use_count is incremented
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << "local pointer in a thread:\n"
                  << "  lp.get() = " << lp.get()
                  << ", lp.use_count() = " << lp.use_count() << '\n';
    }
}

int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();

    std::cout << "Created a shared Derived (as a pointer to Base)\n"
              << "  p.get() = " << p.get()
              << ", p.use_count() = " << p.use_count() << '\n';
    std::thread t1(thr, p), t2(thr, p), t3(thr, p);;
    t1.join(); t2.join(); t3.join();
    std::cout << "All threads completed, the last one deleted Derived\n";
}

代码中英文注释的那段,每个线程的lp变量是共享p变量的,在STL中这种操作是线程安全的。

这里关注下运行结果,在VS2015 x64下跑这段代码,运行结果如下:

Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
p.get() = 0000020D93A291B0, p.use_count() = 1
local pointer in a thread:
lp.get() = 0000020D93A291B0, lp.use_count() = 3
local pointer in a thread:
lp.get() = 0000020D93A291B0, lp.use_count() = 7
local pointer in a thread:
lp.get() = 0000020D93A291B0, lp.use_count() = 3
All threads completed, the last one deleted Derived
Derived::~Derived()
Base::~Base()

可以看到这里us_count跑出来的最大是7(每次跑出的结果都不一样),我预想的最大值应该是4,计算方式我推测应该是如下:一个 main thread + thread 中的三个复制拷贝 + 三个线程代码中 std::shared_ptr lp = p 语句的复制拷贝 = 7(是这样吗?)

借助shared_ptr来实现多线程中的”写时复制”

  • 写时复制的概念:只有在真正需要资源时才去分配资源。”写时复制”应用在多线程同步的场景可以描述如下:
    在系统中,有多个读线程和写线程共享一个数据结构。如果系统中的读操作的概率远远大于写操作,因为读写互斥的需要也会造成读线程间也需要加锁互斥,这无疑降低了系统处理的速度。其实只有当写线程进行操作时,才需要读写线程间的加锁互斥而读线程间是无需加锁的。所以运用写时复制的概念,在写线程判断如果有读线程在操作则拷贝一份数据进行更新。

  • shared_ptr的引用计数的特性可以判断是否有读线程在进行数据读取,来实现写时复制。

示例数据结构如下:

typedef int data;
typedef vecotr<data> datalist;
typedef shared_ptr<datalist> datalistPtr;

datalistPtr g_datas;

读线程代码:

void read()
{
    datalistPtr localDatas;

    {
        lock l(mutex);
        //会增加引用计数
        localDatas = g_datas;       
    }

    for (int i=0; i< localDatas->size(); ++i)
    {
        //读操作
    }
}

读线程中的加锁是针对shared_ptr的,在操作数据时是不用加锁的。

写线程代码:

void write()
{
    lock l(mutex);
    if (!g_datas.unique())
    {//判断是否有读线程操作数据
        datalistPtr newData(new datalist(*g_datas));
        g_datas.swap(newData);
    }

    //对g_datas进行写操作
}

通过unique()判断当前的引用计数是否为1(即没有读线程对g_datas进行操作),如果不为1则复制一份旧数据(交换shared_ptr的指向)进行更新操作。在write函数中的锁必须是全程都锁住的,在有读线程操作数据时则实现对数据的互斥访问(g_datas.unique()为false),在没有读线程操作数据(g_datas.unique()为true)时对shared_ptr的读操作进行互斥。

以上。

资料:
boost shared_ptr官方文档
http://www.boost.org/doc/libs/1_66_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr

shared_ptr< void >作为万能指针的原理
https://stackoverflow.com/questions/5913396/why-do-stdshared-ptrvoid-work#

C++11 shared_ptr官方文档
http://en.cppreference.com/w/cpp/memory/shared_ptr

vs2015 stl多线程安全的描述,里面有对shared_ptr线程安全的描述与C++11标准描述一致
https://msdn.microsoft.com/en-us/library/c9ceah3b(v=vs.140).aspx

猜你喜欢

转载自blog.csdn.net/mo4776/article/details/79605698
今日推荐