对象析构与线程安全
本文从C++程序设计的角度
分析对象在析构
的时候存在的线程安全
问题,并以观察者模式
的实现为例,提出了不同的解决办法。
线程安全问题的暴露
如果将一个对象的指针注册到另一个对象的成员函数回调中,那么在多线程环境下就存在竞态条件
,以观察者模式的实现为例说明,在Observer的基类中,保存了被观察者的指针subject_,用于在本观察者对象析构的时候,从观察列表中取消观察该主题(注释(1)处),用以保证在被观察事件发生的时候通知各个观察者时,保证本观察者存在(注释(2))。
如果Observer对象析构的时候不把本对象指针从观察者列表中移除,那么调用notify的时候,遍历到该对象的指针,由于对象已经析构,指针多指向的对象已经不存在,程序就会出现core dump。
如果在对象析构的时候不移除,而是把列表中对应的指针设置为NULL,仍然不能保证程序按照预期的执行,因为此时可能有新的对象生成,占用了同样的地址空间,那么新对象就会得到通知。
上述要解决的中心问题
就是调用notify 的时候,observer对象是否还存在
,这里可以使用智能指针来解决,依靠引用计数,可以判断当前对象是否还有引用存在,如果有,就不能析构,从而保证了对象的生存期安全。
另外一个
竞态条件是,如果一个线程正要通知的对象,此时正在被另一个线程detach撤销不关注本主题,也即代码中的(2)和(3)
发生竞态条件,这个比较容易解决,可以通过加锁的方式实现。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Observable;
class Observer{
public:
~Observer();
virtual void update() = 0;
void observe(Observable* sub);
private:
Observable* subject_;
};
class Observable{
public:
void attach(Observer* obser){
observerList_.push_back(obser);
}
void detach(Observer* obser){
vector<Observer*>::iterator it = find(
observerList\_.begin(), observerList_.end(), obser);
if(it != observerList_.end()){
swap(*it, observerList_.back());//(3)
observerList_.pop_back();
}
}
void notify(){
vector<Observer*>::iterator it = observerList_.begin();
for(; it != observerList_.end(); ++it){
if(*it) //(2)
(*it)->update();
}
}
private:
vector<Observer*> observerList_;
};
Observer::~Observer(){
subject_->detach(this); //(1)
cout << "Observer destruct" << endl;
}
void Observer::observe(Observable* sub){
subject_ = sub;
subject_->attach(this);
}
class Listener : public Observer{
public:
virtual void update(){
cout << "Listener Update" << endl;;
}
};
使用智能指针和互斥锁解决
上面对问题的描述和解决方法已经描述的比较清楚了,这里通过程序来展示一下修正后的观察者模式。
在观察者数组中使用weak_ptr来代替原生指针,通过引用计数来控制对象的生存期,当引用计数为0时,对象自动销毁,这样就可以保证在调用notify的时候,数组中的观察者对象一定没有被销毁。
这里之所以使用shared_ptr来管理观察者对象,是因为shared_ptr容易造成循环引用,即两个对象互相引用对方,使得最终双方都无法析构。而weak_ptr很好的解决了这个问题,它不增加引用计数,当需要的时候可以上升为shared_ptr,调用成员函数,如果此时对象已析构,则上升失败,智能指针为空。由此可以很方便的判断对象的生存期,并且不需要在析构的时候专门从数组中移除对应的观察者对象指针。
为了保证多线程环境下的观察者列表的线程安全性,增加了互斥锁,在改变该列表状态时都要提前加锁。
class Observable;
class Observer : public enable_shared_from_this<Observer>{
public:
~Observer();
virtual void update() = 0;
void observe(Observable* sub);
private:
Observable* subject_;
};
class Observable{
public:
~Observable(){
cout << "Observable destruct" << endl;
}
void attach(weak_ptr<Observer> obser){
MutexLockGuard lock(mutex_);
observerList_.push_back(obser);
}
void notify(){
MutexLockGuard lock(mutex_);
vector<weak_ptr<Observer> >::iterator it = observerList_.begin();
while(it != observerList_.end()){
shared_ptr<Observer> obj(it->lock());
if(obj){
obj->update();
++it;
}
else{
cout << "observer already destroyed" << endl;
it = observerList_.erase(it);
}
}
}
private:
mutable MutexLock mutex_;
vector<weak_ptr<Observer> > observerList_;
};
Observer::~Observer(){
cout << "Observer destruct" << endl;
}
void Observer::observe(Observable* sub){
subject_ = sub;
sub->attach(shared_from_this());
}
class Listener : public Observer{
public:
virtual void update(){
cout << "Listener Update" << endl;;
}
};
int main(int argc, const char *argv[])
{
Observable subject;
{
shared_ptr<Listener> listener(new Listener);
listener->observe(&subject);
subject.notify();
}
subject.notify();
return 0;
}
智能指针带来的问题
这里使用了weak_ptr,那么程序就必须使用shared_ptr来管理,但是鉴于引用计数的线程安全性,被观察者类的成员函数就必须加锁,这就造成了锁争用,并且notify的争用时间较长,这对程序的效率就会造成影响。