线程安全的观察者模式(智能指针)

与其他面向对象语言不同,C++要求程序员自己去管理对象的生命周期,这就意味着我们总是需要去关注对象的销毁,尤其是在多线程的环境下对象的析构函数编写变得尤为困难,所以在这个方面需要下很大的功夫,否则用C++编写的程序将有很有很多bug。

当异构函数遇到多线程

在阅读文章之前我先提出两个问题:

在析构一个对象的时候,从何而知此时是否有别的线程正在执行该对象的成员函数?

调用某个对象的成员函数之前,如何知道对象还活着?它的析构函数会不会碰巧执行到一半?

这些问题是多线程编程面临的基本问题,本文将从这些问题展开讨论,并最终通过智能指针解决这些问题。

下面是观察者模式的伪代码,下面代码有很多问题,单线程下会出现空悬指针和野指针问题,多线程下存在太多的竞态问题。

class Observable
{
public:
	void register_(Observer* x)
	{
		push_back....
	}
	void notifyObservers()
	{
		for(Observer* x : observers_)
		{
			x->update();
		}
	}
private:
	std::vector<Observer*> observer_;
}



class Observer//观察者
{
public:
	Observer();
	void observer(Observable* s)
	{
		s->register_(this);
	}
	void update()
	{
		dosomething
		....
	}
}


Observer* a = new Observer();
Observer* a1 = new Observer();
Observable* s = new Observable();
a->observer(s);
a1->observer(s);

如果给Observable提供一个unregister函数,并在observer对象销毁的时候调用这个函数,将vector数组里面的指针erase,这样看上去好像可以解决。

Observable::
void unregister(Observer* x)
	{
		erase.....
	}


Observer::

~Observer()
{
    unregister(this);
}

这里就会出现文章开头的两个问题。下面是全部代码(单线程没问题):

class Observable
{
public:
	void register_(Observer* x)
	{
		push_back....
	}
	void unregister(Observer* x)
	{
		erase.....
	}
	void notifyObservers()
	{
		for(Observer* x : observers_)
		{
			x->update();
		}
	}
private:
	std::vector<Observer*> observer_;
}



class Observer//观察者
{
public:
	Observer();
	void observer(Observable* s)
	{
		s->register_(this);
		subject_ = s;
	}
	void update()
	{
		dosomething
		....
	}
	~Observer()
	{
		subject_->unregister(this);
	}
private:
	Observable* subject_;
}

mutex不是答案

上述问题就是多线程编程下的竞态问题(race condition),如果线程A中的Observer对象还没析构,线程B调用update(),这个时候线程A开始析构对象了,这样会发生什么没人会知道。

这些问题好像可以通过加锁来解决,但在哪加锁,谁去持有锁,如果Observer对象去持有锁,它析构的过程也会销毁锁,那么调用unregister之前锁被销毁了亦或者是没有被销毁,没人知道,所以通过加锁来解决显然不太好实现。

如果有活着的对象能帮我们就好了,它提供一个isAlive()之类的程序函数,告诉我们对象还在不在,可惜引用和指针不行。

shared_ptr/weak_ptr

标准库里面的shared_ptr是一个类模板,它是一个引用计数型智能指针,当引用计数降为0时,对象就会被销毁。shared_ptr的 “计数” 在主流平台上面是原子操作,没有锁,性能不俗。

shared_ptr控制对象的生命期,只要有一个指向x对象的shared_ptr存在,该x对象就不会析构,当指向对象x的最后一个shared_ptr析构或者reset()的时候,x才会被销毁。

weak_ptr不控制对象的生命期,但是它知道对象是否还活着:weak_ptr通过lock()提升为有效的shared_ptr,并且lock()这个行为也是线程安全的。

于是把智能指针应用到Observer上来:

class Observable
{
public:
	void register_(weak_ptr<Observer> x);
	//void unregister(weak_ptr<Observer> x); // 不需要它
	void notifyObservers();
	
private:
	typedef std::vector<weak_ptr<Observer>>::iterator Iterator;
	std::vector<weak_ptr<Observer>> observers_;
	std::mutex mtx;
};



void Observable::notifyObservers()
{
	mtx.lock();
	Iterator it = observer_.begin();
	while(it != observers_end())
	{
		shared_ptr<Observer> obj(it->lock()); //尝试提升,这一步是线程安全的
		if(obj)
		{
			//提升成功,这里的obj是在栈上新建的,所以没有竞态关系,
			//全局共享的那个Observer对象是否被销毁对它没影响
			obj->update();
			++it;
		}
		else
		{
			//对象已经被销毁了,从容器中拿掉weak_ptr
			it = observers_.erase(it);
		}
	}
    mtx.unlock();
}

这里的关键是weak_ptr提升到shared_ptr这一步线程安全,提升后的对象是一个栈对象,对象在本作用域一直存在,对象调用update方法不会有任何竞态关系,当对象离开作用域就会销毁对象。

真的解决了吗?

好像确实有解决文章开头提出的问题,但是这样又有了其他问题,如下:

锁占用的时间太长了,update的时间如果很长程序未免太浪费时间。

如果给Observer提供一个接口去调用subject_->unregister(this),那么subject_肯定也需要用weak_ptr<Observable>来管管理。

如果update里面有调用register呢,这就会产生死锁(如果锁是不可重入的)。

当然这只是针对文章开头的问题提出的解决方案,具体还得看具体场景,针对不同得场景会有不同得设计方法。

总结

文章围绕着经典的观察者模式来讲解智能指针的威力,看到他在多线程里面的一些用途,但是对于智能指针得认识有限,无法再深入展开。待深入理解指针指针后,会写出更多有关它得一些文章。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值