欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

shared_ptr应用细节

程序员文章站 2024-02-29 09:38:10
...

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://*.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

相关标签: boost shared_ptr