文章全部内容来源于陈硕大佬的《Linux多线程服务端编程 使用muduo网络库》,此为个人读书笔记
讨论多线程安全,通过对观察者模式的多线程版本的逐步改写,介绍互斥器(mutex)、智能指针作用和用法
智能指针解决了什么问题?:
构造函数只要不泄漏this指针,加锁就可以保证线程安全;
析构则不然,会出现A线程析构对象的时候,B正好在访问该对象,且在B判断该对象是否被释放的时候,A还没来得及释放,这样B就会访问一个混乱的地址内容。(空悬指针)
引入智能指针的概念,其实就是引用技术,中间加了一个中间层proxy,这个proxy包含一个指向真是对象的指针,和一个引用计数器,帮助做垃圾回收。
– weak_ptr:不控制对象生命周期,但能检测对象是否还活着。如果活着这可以提升为有效的shared_ptr。如果死了提升就失败,范围一个空的shared_ptr。
– shared_ptr:强引用,控制对象生命期。
MutexLockGuard lock(mutex_);
lock的析构会晚于返回对象的构造,因此有效地保护了这个共享数据。
weak_ptr解决观察者模式多线程问题:
1.原始观察者模式:
//可观察者类
class Observable{
public:
void register_(Observer* x);
void unregister(Observer* x);
void notifyObservers(){
for(Observer* x : observers_){
x->updata(); //***
}
}
private:
vector<Observer*> observers_;
};
//观察者
class Observer{
public:
Observer(Observable* s){
s->register_(this);
subject_ = s;
}
~Observer(){
subject_ ->unregister(this); //*****
}
Observable* subject_;
};
存在问题:
A线程执行到26行**处时,没有来得及注销掉当前的观察者,而同时有个B线程执行到9行,即处,正好指向A正在析构的对象,这样就会更新一个正在析构的对象,会发生什么难以预计。
2.引入weak_ptr解决Observer竞态问题
用智能指针改写Observable类
class Observable{
public:
void register_(weak_ptr<Observer> x);
void notifyObservers();
private:
mutable MutexLock mutex_;
std::vector<weak_ptr<Observer>> observers_;
typedef std::vector<weak_ptr<Observer>>::iterator Iterator;
};
void Observable::notifyObservers() {
MutexLockGuard lock(mutex_); //加锁
Iterator it = observers_.begin(); //weak_ptr迭代器
while(it != obervers_.end()){
shared_ptr<Observer> obj(it->lock()); //尝试提升,这一步是线程安全的
if(obj){
//提升成功
obj->update(); //***
++it;
}else{
//提升失败,从容器中拿掉weak_ptr
it-<observers_.erase(it);
}
}
}
存在问题,第19行,***处。如果Update之中调用了register或者unregister,且mutex不可重入,则可能会死锁。(注:可重入 就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。)
3.完全解决Oberver模式问题 – 绕开它
使用Signal/Slots,详见书本p29
shared_ptr特性:
1.因为shared_ptr里面有两个成员,一个是计数器一个是指向底层对象的指针。所以shared_ptr本身没法搞原子操作,是线程不安全的。(但它指向的对象是线程安全的)。
即–
一个shared_ptr对象实体可被多个线程同时读取。
两个shared_ptr对象实体可以被两个线程同时写入,析构算写操作。
如果要从多个线程读写同一个shared_ptr对象,那么需要加锁。
2.shared_ptr要小心复制,如vector中的,boost::bind中的
class StockFactory:boost::noncopyable{
public:
shared_ptr<Stock> get(const string& key);
private:
mutable MutexLock mutex_;
std::map<string,shared_ptr<Stock>> stocks_;
};
因为map里面的值是shared_ptr,所以stock永远有引用,永远不会被销毁。
所以需要用weak_ptr做弱绑定,且重写get函数。
下面这段代码就是弱回调。
class StockFactory:boost::noncopyable{
public:
shared_ptr<Stock> get(const string& key);
private:
mutable MutexLock mutex_;
std::map<string,shared_ptr<Stock>> stocks_;
void deleteStock(Stock* stock){
if(stock){
MutexLockGuard lock(mutex_);
stocks_erase(stock->key());
}
delete stock;
}
};
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(); //尝试提升,如果有这个key则直接返回
//否则需要删除map里面的值
if(!pStock){
pStock.reset(new Stock(key),boost::bind(&StockFactory::deleteStock,shared_form_this(),_1));
wkStock = pStock;
}
return pStock;
}
这部分代码主要是get这边用weak_ptr做了个提升检测,还有就是reset这里,做了个在没有引用时删除map的操作。
特别的:
shared_form_this() --》指向当前对象的shared_ptr的对象,类似于this
最后:作者建议少用跨线程对象