Chapter 1-02

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

1.8 应用到 Observer 上

?既然通过 weak_ptr 能探查对象的生死,那么 Observer 模式的竞态条件就很容易解决,只要让Observable 保存 weak_ptr 即可:

?前文代码 (3) 处(p. 10 的 L17)的竞态条件已经弥补了。

?思考:如果把 L48 改为 vector

void read()
{
shared_ptr<Foo> localPtr;
{
MutexLockGuard lock(mutex);
localPtr = globalPtr; // read globalPtr
}
// use localPtr since here,读写 localPtr 也无须加锁
doit(localPtr);
}

?写入的时候也要加锁:

void write()
{
shared_ptr<Foo> newPtr(new Foo); // 注意,对象的创建在临界区之外
{
MutexLockGuard lock(mutex);
globalPtr = newPtr; // write to globalPtr
}
// use newPtr since here,读写 newPtr 无须加锁
doit(newPtr);
}

?read()和write()在临界区之外都没有再访问globalPtr,而是用了一个指向同一Foo对象的栈上shared_ptr local copy。只要有这样的local copy 存在,shared_ptr 作为函数参数传递时不必复制,用 reference to const 作为参数类型即可。

?上面的new Foo是在临界区之外执行的,这种写法比在临界区内写 globalPtr.reset(new Foo) 要好,因为缩短了临界区长度。

?如果要销毁对象,虽然我们可以在临界区内执行globalPtr.reset(),但是这样往往会让对象析构发生在临界区以内,增加了临界区的长度。改进办法是定义一个localPtr,用它在临界区内与globalPtr交换(swap()),这样能保证把对象的销毁推迟到临界区之外。练习:在write()函数中,globalPtr = newPtr; 这一句有可能会在临界区内销毁原来globalPtr指向的Foo对象,设法将销毁行为移出临界区。

1.10 shared_ptr 技术与陷阱

意外延长对象的生命期

?shared_ptr是强引用,只要有一个指向x对象的shared_ptr存在,该对象就不会析构。而shared_ptr允许拷贝构造和赋值,如果遗留了一个拷贝,那么对象就永存了。例如如果把p.16中L48 observers_的类型改为 vector

Observer::~Observer()
{
    subject_->unregister(this);
}
void Observable::unregister(Observer* x)
{
    std::vector<Observer*>::iterator it =
std::find(observers_.begin(), observers_.end(), x);
    if (it != observers_.end())
    {
        std::swap(*it, observers_.back());
        observers_.pop_back();
    }
}

?另一个出错的可能是boost::bind,因为 boost::bind 会把实参拷贝一份,如果参数是个 shared_ptr,那么对象的生命期就不会短于 boost::function 对象:

class Foo
{
    void doit();
};
shared_ptr<Foo> pFoo(new Foo);
boost::function<void()> func = boost::bind(&Foo::doit, pFoo);  // long life foo

?这里 func 对象持有了 shared_ptr 的一份拷贝,有可能会在不经意间延长倒数第二行创建的 Foo 对象的生命期。

函数参数

?因为要修改引用计数,而且拷贝的时候通常要加锁,shared_ptr的拷贝开销比拷贝原始指针要高,但是需要拷贝的时候并不多。多数情况下它可以以const reference方式传递,一个线程只需要在最外层函数有一个实体对象,之后都可以用const reference来使用这个shared_ptr。例如有几个函数都要用到Foo对象:

void save(const shared_ptr<Foo>& pFoo);  // pass by const reference
void validateAccount(const Foo& foo);
bool validate(const shared_ptr<Foo>& pFoo)  // pass by const reference
{
validateAccount(*pFoo);
// ...
}

?在通常情况下,我们可以传常引用(pass by const reference):

void onMessage(const string& msg)
{
//只要在最外层持有一个实体,无安全问题
shared_ptr<Foo> pFoo(new Foo(msg));
if (validate(pFoo))  // 没有拷贝 pFoo
{
save(pFoo);  // 没有拷贝 pFoo
}
}

?遵照这个规则,基本上不会遇到反复拷贝 shared_ptr 导致的性能问题。由于pFoo是栈上对象,不可能被别的线程看到,那么读取始终是线程安全的。

析构动作在创建时被捕获

?这是一个非常有用的特性,这意味着:

1.虚析构不再是必需的。

2.shared_ptr 可以持有任何对象,而且能安全地释放。

3.shared_ptr对象可以安全地跨越模块边界,比如从 DLL 里返回,而不会造成从模块 A 分配的内存在模块 B 里被释放这种错误。

4.二进制兼容性,即便 Foo 对象的大小变了,那么旧的客户代码仍然可以使用新的动态库,而无须重新编译。前提是 Foo 的头文件中不出现访问对象的成员的inline 函数,并且 Foo 对象由动态库中的 Factory 构造,返回其 shared_ptr。

5.析构动作可以定制。

?最后特性的实现比较巧妙,因为shared_ptr只有一个模板参数,而“析构行为”可以是函数指针、仿函数(functor)或者其他什么东西。这是泛型编程和面向对象编程的一次完美结合。有兴趣的读者可以参考 Scott Meyers 的文章http://www.artima.com/cppsource/top_cpp_aha_moments.html。这个技术在后面的对象池中会用到。

析构所在的线程

?对象的析构是同步的,当最后一个指向 x 的 shared_ptr 离开其作用域的时候, x 会同时在同一个线程析构。这个线程不一定是对象诞生的线程。

?这个特性是把双刃剑:

1.弊:如果最后一个shared_ptr引发的析构发生在关键线程,并且对象的析构比较耗时,那么可能会拖慢关键线程的速度;

2.利:可以用一个单独的线程来专门析构,通过一个BlockingQueue

// version 1: questionable code
class StockFactory : boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key);
private:
mutable MutexLock mutex_;
std::map<string, shared_ptr<Stock> > stocks_;
};

?get():如果在stocks_里

—找到了key,就返回stocks_[key];

—没找到key,新建一个Stock,并存入stocks_[key]。

?问题:Stock对象永远不会被销毁,因为map里存的是shared_ptr。应该存weak_ptr?

// // version 2: 数据成员修改为 std::map<string, weak_ptr<Stock> > stocks_;
shared_ptr<Stock> StockFactory::get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLockGuard lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key];
// 如果key不存在,会默认构造一个
pStock = wkStock.lock(); // 尝试把“棉线”提升为“铁丝”
if (!pStock)
{
pStock.reset(new Stock(key));
wkStock = pStock; // 这里更新了 stocks_[key],注意 wkStock 是个引用
}
return pStock;
}

?这么做Stock对象是销毁了,但是程序出现了内存泄漏。因为stocks_的大小只增不减,stocks_.size()是曾经存活过的Stock对象的总数,即使活的Stock对象数目降为0。

?考虑到世界上的股票数目是有限的,这个内存不会一直泄漏下去,最多把每只股票的对象都创建一遍,泄漏的内存也只有几兆字节。如果这是一个其他类型的对象池,对象的key集合不是封闭的,内存就会一直泄漏下去。

?解决办法:利用 shared_ptr 的定制析构功能。shared_ptr 的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数d,在析构对象时执行d(ptr),其中 ptr 是 shared_ptr 保存的对象指针。这么设计不是多余的,因为要在创建对象时捕获释放动作,始终需要一个 bridge。

template<class Y, class D>
shared_ptr::shared_ptr(Y* p, D d);
template<class Y, class D>
void shared_ptr::reset(Y* p, D d);

Y的类型可能与T不同,只要 Y* 能隐式转换为 T*,就是合法的,。

?我们可以利用这一点,在析构 Stock 对象的同时清理 stocks_。

// version 3
class StockFactory : boost::noncopyable
{
// 在 get() 中,将 pStock.reset(new Stock(key)); 改为:
// pStock.reset(new Stock(key),
//                      boost::bind(&StockFactory::deleteStock, this, _1));

private:
void deleteStock(Stock* stock)
{
if (stock)
{
MutexLockGuard lock(mutex_);
stocks_.erase(stock->key());
}
delete stock; // sorry, I lied
}
// assuming StockFactory lives longer than all Stock‘s ...
// ...
}

?这里向pStock.reset()传递了第二个参数,一个boost::function,让它在析构Stock* stock时调用本StockFactory对象的deleteStock成员函数。

?问题:我们把一个原始的 StockFactory this 指针保存在了 boost::function 里,这有线程安全问题。如果这个 StockFactory 先于 Stock 对象析构,那么会 core dump。类比Observer在析构函数里去调用 Observable::unregister(),而那时 Observable 对象可能已经不存在了。这要用到 § 1.11.2 介绍的弱回调技术解决。

1.11.1 enable_shared_from_this

?StockFactory::get()把原始指针this保存到了boost::function中,如果StockFactory的生命期比Stock短, 那么Stock析构时去回调StockFactory::deleteStock就会 core dump。我们应该用shared_ptr来解决对象生命期问题,因为StockFactory::get() 本身是个成员函数,所以要获得一个指向当前对象的shared_ptr对象。

?用enable_shared_from_this。这是一个以其派生类为模板类型实参的基类模板14 http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern,继承它,this指针就能变身为shared_ptr。

class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{ /* ... */ };

?为了使用shared_from_this(),StockFactory不能是stack object,必须是heap object且由shared_ptr管理其生命期,即:

shared_ptr stockFactory(new StockFactory);

?可以让this变化为 shared_ptr 了。

// version 4
shared_ptr<Stock> StockFactory::get(const string& key)
{
//  change
//  pStock.reset(new Stock(key),
//              boost::bind(&StockFactory::deleteStock, this, _1));
//  to
pStock.reset(   new Stock(key),
boost::bind (   &StockFactory::deleteStock,
shared_from_this(),
_1));
// ...

?这样boost::function里保存了一份shared_ptr,可以保证调用StockFactory::deleteStock的时候StockFactory对象还活着。

?shared_from_this()不能在构造函数里调用,因为在构造的时候,对象还没有被交给shared_ptr接管。

?最后一个问题,StockFactory的生命期被意外延长了。

1.11.2 弱回调

?把shared_ptr绑(boost::bind)到boost::function里,那么回调的时候StockFactory对象始终存在,是安全的。这同时延长了对象的生命期,使之不短于绑得的boost::function对象。

?有时需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,就像Observable::notifyObservers()那样,这称为“弱回调”。我们可以把weak_ptr绑到 boost::function里,这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为shared_ptr,如果提升成功,说明接受回调的对象还存在,那么就执行回调;如果提升失败,不执行操作。

?使用这一技术的完整 StockFactory 代码如下:

class StockFactory : public boost::enable_shared_from_this<StockFactory>,
boost::noncopyable
{
public:
shared_ptr<Stock> get(const string& key)
{
shared_ptr<Stock> pStock;
MutexLockGuard lock(mutex_);
weak_ptr<Stock>& wkStock = stocks_[key]; // 注意 wkStock 是引用
pStock = wkStock.lock();
if (!pStock)
{
pStock.reset(new Stock(key),
boost::bind
(&StockFactory::weakDeleteCallback,
boost::weak_ptr<StockFactory>(shared_from_this()),
_1));
// 必须强制把shared_from_this()转型为weak_ptr,才不会延长生命期,
// 因为 boost::bind 拷贝的是实参类型,不是形参类型
wkStock = pStock;
}
return pStock;
}
private:
static void weakDeleteCallback( const boost::weak_ptr<StockFactory>& wkFactory,
  Stock* stock)
{
shared_ptr<StockFactory> factory(wkFactory.lock()); // 尝试提升
if (factory) // 如果 factory 还在,那就清理 stocks_
{
factory->removeStock(stock);
}
delete stock; // sorry, I lied
}
void removeStock(Stock* stock)
{
if (stock)
{
MutexLockGuard lock(mutex_);
stocks_.erase(stock->key());
}
}
private:
mutable MutexLock mutex_;
std::map<string, weak_ptr<Stock> > stocks_;
};

两个简单的测试:

void testLongLifeFactory()
{
shared_ptr<StockFactory> factory(new StockFactory);
{
shared_ptr<Stock> stock = factory->get("NYSE:IBM");
shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
assert(stock == stock2);
// stock destructs here
}
// factory destructs here
}
void testShortLifeFactory()
{
shared_ptr<Stock> stock;
{
shared_ptr<StockFactory> factory(new StockFactory);
stock = factory->get("NYSE:IBM");
shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
assert(stock == stock2);
// factory destructs here
}
// stock destructs here
}

Code

#include <map>

#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

#include "mutex.h"

#include <stdio.h>

using std::string;

class Stock
{
private:
    string name_;

public:
    Stock(const string &name) : name_(name)
    {
        printf("Constructor:\tStock[%p] %s\n", this, name_.c_str());
    }

    ~Stock()
    {
        printf("Destructor:\t~Stock[%p] %s\n", this, name_.c_str());
    }

    const string &key() const
    {
        return name_;
    }
};

class StockFactory : public boost::enable_shared_from_this<StockFactory>
{
private:
    mutable MutexLock mutex_;
    std::map<string, boost::weak_ptr<Stock> > stocks_;

    static void deleteStock(const boost::weak_ptr<StockFactory> &wkFactory, Stock *stock)
    {
        printf("weakDeleteStock[%p]\n", stock);
        boost::shared_ptr<StockFactory> factory(wkFactory.lock());
        if(factory)
        {
            factory->removeStock(stock);
        }
        else
        {
            printf("factory died.\n");
        }
        delete stock;
    }

    void removeStock(Stock *stock)
    {
        if(stock)
        {
            MutexLockGuard lock(mutex_);
            stocks_.erase(stock->key());
        }
    }

public:
    StockFactory()
    {
        printf("Constructor:\tStockFactory[%p]\n", this);
    }

    ~StockFactory()
    {
        printf("Destructor:\t~StockFactory[%p]\n", this);
    }

    boost::shared_ptr<Stock> get(const string &key)
    {
        MutexLockGuard lock(mutex_);
        boost::weak_ptr<Stock> &wkStock = stocks_[key];
        boost::shared_ptr<Stock> pStock = wkStock.lock();
        if(!pStock)
        {
            pStock.reset(new Stock(key),
                            boost::bind(&StockFactory::deleteStock,
                                        boost::weak_ptr<StockFactory>(shared_from_this()),
                                        _1));
            wkStock = pStock;
        }
        return pStock;
    }
};

void testLongLifeFactory()
{
    boost::shared_ptr<StockFactory> factory(new StockFactory);
    {
        boost::shared_ptr<Stock> stock = factory->get("NYSE:IBM");
        boost::shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
        if(stock == stock2)
        {
            printf("Good:\tstock == stock2\n");
        }
        // stock destructs here
    }
    // factory destructs here
}

void testShortLifeFactory()
{
    boost::shared_ptr<Stock> stock;
    {
        boost::shared_ptr<StockFactory> factory(new StockFactory);
        stock = factory->get("NYSE:IBM");
        boost::shared_ptr<Stock> stock2 = factory->get("NYSE:IBM");
        if(stock == stock2)
        {
            printf("Good:\tstock == stock2\n");
        }
        // factory destructs here
    }
    // stock destructs here
}

int main()
{
    boost::shared_ptr<StockFactory> factory(new StockFactory);
    printf("\nAAAAAAAAAAAAAAAAAAAAAA\n");
    {
        boost::shared_ptr<Stock> stock = factory->get("stock");
    }
    printf("BBBBBBBBBBBBBBBBBBBBBBBB\n");

    printf("\nCCCCCCCCCCCCCCCCCCCCCCCC\n");
    testLongLifeFactory();
    printf("DDDDDDDDDDDDDDDDDDDDDD\n");

    printf("\nEEEEEEEEEEEEEEEEEEEEEEEEEE\n");
    testShortLifeFactory();
    printf("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n");

    return 0;
}

?这下完美了,无论Stock和StockFactory谁先挂掉都不会影响程序的正确运行。

?通常Factory对象是个singleton,在程序正常运行期间不会销毁,这里只是为了展示弱回调技术15(通用的弱回调封装见recipes/thread/WeakCallback.h,用到了 C++11的variadic template和rvalue reference),这个技术在事件通知中非常有用。

?本节的StockFactory只有针对单个Stock对象的操作,如果程序需要遍历整个stocks_,稍不注意就会造成死锁或数据损坏(§ 2.1) ,请参考 § 2.8 的解决办法。

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

时间: 2024-08-10 23:08:23

Chapter 1-02的相关文章

PHP开发微信支付功能

因工作需要,公司有一个项目要使用到微信扫码付款功能 01.登录微信公众号,下载DEMO程序https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1 02.打开demo的 03.修改配置文件每一个公众号后台有这四个相关配置参数 04.修改扫码支付连接 05.打开扫码连接,会发现写某讯API部门的这群杂毛写的代码有问题的,需要修改.... 打开这个API程序F:\wamp\www\www.wxpay.cn\lib\WxPay.Api

百度刚放假啊数据库风口浪尖萨拉疯了

http://www.ebay.com/cln/l_x5585/2015.02.11/176746639012 http://www.ebay.com/cln/jiacha_boryk/2015.02.11/176837188016 http://www.ebay.com/cln/gbnlin0/2015.02.11/176837189016 http://www.ebay.com/cln/j_j2841/2015.02.11/177066749015 http://www.ebay.com/c

百度房间爱师傅卡卡是快乐疯了;爱死

http://www.ebay.com/cln/shx9479/-/177007606013/2015.02.11 http://www.ebay.com/cln/genqi12/-/176846034010/2015.02.11 http://www.ebay.com/cln/seyyon2/-/176906811016/2015.02.11 http://www.ebay.com/cln/wcn5971/-/176846032010/2015.02.11 http://www.ebay.co

百度和房价是否健康教案上开发

http://www.ebay.com/cln/l.kuan2/-/167247714018/2015.02.10 http://www.ebay.com/cln/setlia-3616/-/167086016019/2015.02.10 http://www.ebay.com/cln/pen-y77/-/167086017019/2015.02.10 http://www.ebay.com/cln/yua-me2/-/167399441016/2015.02.10 http://www.eba

百度电话费健身房拉伸件礼服加拉斯减肥

http://www.ebay.com/cln/cnli_c90nphs5e/-/167379958016/2015.02.07 http://www.ebay.com/cln/gaw4612/-/167226239018/2015.02.07 http://www.ebay.com/cln/re_len4/-/167263594010/2015.02.07 http://www.ebay.com/cln/ta.ku83/-/167162702017/2015.02.07 http://www.

百度回复金卡是减肥拉进来收付款

http://www.ebay.com/cln/cnli_c90nphs5e/-/167379958016/2015.02.08 http://www.ebay.com/cln/gaw4612/-/167226239018/2015.02.08 http://www.ebay.com/cln/re_len4/-/167263594010/2015.02.08 http://www.ebay.com/cln/ta.ku83/-/167162702017/2015.02.08 http://www.

百度放假哈萨克就发了设计费拉萨

http://www.ebay.com/cln/ldicn.mz6dm/2015.02.11/177030163015 http://www.ebay.com/cln/tan_qi5/2015.02.11/176903144013 http://www.ebay.com/cln/l.lu104/2015.02.11/177030175015 http://www.ebay.com/cln/ya01191/2015.02.11/176722580014 http://www.ebay.com/cl

百度房间撒谎发卡上就发了空间啊

http://www.ebay.com/cln/h-h4129/2015.02.11/176819191016 http://www.ebay.com/cln/fendo88/2015.02.11/176613943017 http://www.ebay.com/cln/ygon288/2015.02.11/176727517018 http://www.ebay.com/cln/ta.ch17/2015.02.11/176613950017 http://www.ebay.com/cln/g-

百度房间沙发客服就考试考几分离开

http://www.ebay.com/cln/jinlon8/book/167309734010/2015.02.10 http://www.ebay.com/cln/bam5330/book/167115292019/2015.02.10 http://www.ebay.com/cln/yi_za70/book/167315676012/2015.02.10 http://www.ebay.com/cln/y.y3463/book/167285977014/2015.02.10 http:/

Web Service学习笔记之----JAX-RPC

众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容.此次课程以<星际争霸II>回放文件分析为例,集中在IBM Cloud相关数据分析服务的应用.面对星际游戏爱好者希望提升技能的要求,我们使用IBM Data Science Experience中的jJupyter Notebooks来实现数据的可视化以及对数据进行深度分析,并最终存储到IBM Cloudant中.这是个介绍+动手实践的教程,参会者不仅将和讲师一起在线