C++学习:线程安全的对象

线程安全的对象

析构函数遇到多线程

竞态条件:C++的对象生命周期由程序员管理,当一个对象被多个线程同时看到时,对象被谁销毁,何时销毁不清楚。

解决方法:shared_ptr.

线程安全的类:
1.多个线程同时访问时,其表现出正确的行为
2.无论操作系统如何调度这些线程,无论这些线程执行顺序如何交织。
3.调用端代码无须额外的同步和其他协调动作。

C++标准库里的大多数类都不是线程安全的,包括string、vector、map等,多线程使用时要加锁。

mutable修饰的成员变量,可以被常成员函数修改。

对象的构造做到线程安全很简单,构造函数中不要泄露this指针即可。
但对象析构,在单线程里,最多需要注意避免空悬指针(指向的对象已经销毁)和野指针(指针未被初始化)。但在多线程程序中,比较复杂,普通函数可以使用成员变量互斥锁,但析构函数会把互斥锁析构掉。

智能指针在Observer中的应用

定义对象时,采用shared_ptr, 其他地方引用对象时采用weak_ptr,弱智能指针无法直接访问资源,需要先提升为强智能指针。weak_ptr可以观察shared_ptr引用的对象是否存在,来判断是否能够提升。

#include <algorithm>
#include <vector>
#include <stdio.h>
#include "../Mutex.h"
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class Observable;

class Observer : public boost::enable_shared_from_this<Observer>
{
 public:
  virtual ~Observer();
  virtual void update() = 0;

  void observe(Observable* s);

 protected:
  Observable* subject_;
};

class Observable
{
 public:
  void register_(boost::weak_ptr<Observer> x);
  // void unregister(boost::weak_ptr<Observer> x);

  void notifyObservers()
  {
    muduo::MutexLockGuard lock(mutex_);
    Iterator it = observers_.begin();
    while (it != observers_.end())
    {
      boost::shared_ptr<Observer> obj(it->lock());
      if (obj)
      {
        obj->update();
        ++it;
      }
      else
      {
        printf("notifyObservers() erase\n");
        it = observers_.erase(it);
      }
    }
  }

 private:
  mutable muduo::MutexLock mutex_;
  std::vector<boost::weak_ptr<Observer> > observers_;
  typedef std::vector<boost::weak_ptr<Observer> >::iterator Iterator;
};

Observer::~Observer()
{
  // subject_->unregister(this);
}

void Observer::observe(Observable* s)
{
  s->register_(shared_from_this());
  subject_ = s;
}

void Observable::register_(boost::weak_ptr<Observer> x)
{
  observers_.push_back(x);
}

//void Observable::unregister(boost::weak_ptr<Observer> x)
//{
//  Iterator it = std::find(observers_.begin(), observers_.end(), x);
//  observers_.erase(it);
//}

// ---------------------

class Foo : public Observer
{
  virtual void update()
  {
    printf("Foo::update() %p\n", this);
  }
};

int main()
{
  Observable subject;
  {
    boost::shared_ptr<Foo> p(new Foo);
    p->observe(&subject);
    subject.notifyObservers();
  }
  subject.notifyObservers();
}

存在问题:
不是完全线程安全: subject也应交给智能指针管理
锁争用(lock contention): obserable三个成员函数都用一个锁,等待时间可能会很长。
死锁: 如果虚函数update中调用了(un)register,mutex如果不可重入,则会死锁。可重入的话,面临迭代器失效问题。(一种办法:使用可重入锁, vector换成list,it++提前一行。)
注:可重入锁,如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。

再论shared_ptr

借助shared_ptr实现其他类的线程安全,但shared_ptr本身不是完全线程安全的。在多个线程同时访问同一个shared_ptr时,要使用mutex保护。

MutexLock mutex;
shared_ptr<Fool> globalPtr;
void doit(const shared_ptr<Foo>& pFoo);

void read()
{
    shared_ptr<Foo> localPtr;
    {
        MutexLockGuard lock(mutex);
        localPtr = globalPtr;  // read
    }
    doit(localPtr);
}

void write()
{
    shared_ptr<Foo> newPtr(new Foo);
    {
        MutexLockGuard lock(mutex);
        globalPtr = newPtr;  // write
    }
    doit(newPtr);
}

上述代码在临界区之外都没有访问globalPtr,缩短临界区长度

RAII(资源获取即初始化):new完对象后,应立刻将资源交给shared_ptr(handle对象)。
使用shared_ptr要避免循环引用,通常做法是owner持有指向child的shared_ptr,child持有指向owner的weak_ptr. (定义对象时,采用shared_ptr, 其他地方引用对象时采用weak_ptr)

对象池

class StockFactory : boost::noncopyable
{
public:
    shared_ptr<Stock> get(const string& key);

private:
    mutable MutexLock mutex_;
    std::map<string, weak_ptr<Stock> > stocks_;
};

shared_ptr<Stock> StockFacory::get(const string& key) 
{
    shared_ptr<Stock> pStock;
    MutexLockGuard lock(mutex_);
    weak_ptr<Stock>& wkStock = stocks_[key];
    pStock = wkStock.lock();
    if(!pStock) {
        pStock.reset(new Stock(key));
        wkStock = pStock;
    }
    return pStock;
}

Stock对象销毁了,但stocks_的大小没变,程序运行过程中只增不减。解决方案:
采用shared_ptr的定制析构功能,shared_ptr构造函数中传入一个函数指针或函数对象,在析构对象时执行。

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;
    }
}

reset中将StockFactory的原始指针this传给Stock,如果StockFactory生命周期比Stock短,就会core dump。
在成员函数中获得当前对象的shared_ptr:enable_shared_from_this

class StockFactorypublic boost::enable_shared_from_this<StackFactory>,
                      boost::noncopyable
{/*...*/};

/*
pStock.reset(new Stock(key),
                boost::bind(&StockFactory::deleteStock, shared_from_this(), _1));
*/

使用shared_from_this,可能会延长StockFactory的生命周期,使之不得短于boost::bind()函数对象。解决方法:
利用weak_ptr.(弱回调)

回调时先尝试提升为shared_ptr,提升成功,执行回调,否则什么都不做。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值