线程安全的对象
析构函数遇到多线程
竞态条件: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 StockFactory : public 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,提升成功,执行回调,否则什么都不做。