这是我学习muduo库的第二个阶段,前面一个阶段主要阅读了网络库部分的源码,大致了解网络通信各部分的细节。
编写线程安全的类不是难事,用同步原语(互斥量,条件变量,信号量等等) 保护内部状态即可。但是对象的生与死不能由对象自身的mutex来保护(△)。如何避免对象析构时存在的竞争条件(race condition)是C++多线程编程面临的基本问题,正确的答案是Boost库中的shared_ptr和weak_ptr(C++11中加入标准库)。
这篇Blog的写作目的是在看完《Linux多线程服务端编程》第一章之后的总结与体会,免得白看。接下来我将
由于C++要求程序员自己管理对象的生命期,这在多线程就显得尤为困难。特别是当一个对象能被多个线程同时看到时,就会出现所谓的竞争条件(race condition):
我们拿Observer模式来看看race condition。所谓Observer模式,也叫观察者模式,是一种一对多的设计模型,当Observable(被观察者)变化时,会主动更新与其关联的Observer(观察者),代码如下:
//观察者
class Observer {
public:
~Observer();
void update();
};
//观察目标
class Obervable {
public:
void notifyObservers() {
for (Observer* x : observers_) {
x->update();
}
}
private:
std::vector observers_;
};
当观察目标变化,调用notifyObservers时,如何得知Observer对象还活着?一种方法是在Observer析构函数中调用Observable::unregister()函数来解注册,但仍然存在问题,如何确保此时的Observable的对象还有有效的?如果一个线程正在析构一个Observer,而另一个线程却调用x->update()来更新该Observer,又会产生什么样的后果?
因此我想要这样一个成员函数isAlive(),它可以告诉我当前我想要调用的对象是不是还存活的。
其次再来谈谈(△)问题。如果添加一个互斥锁成员变量,那么各个线程在访问一个对象时就要拿到该锁的使用权才能继续进行某些访问。看起来似乎不错,但是这一前提是要求锁必须是有效的,但在析构函数却破坏了这一个假设,它会把mutex成员变量销毁掉。如果另外一个线程原先是正在等待该锁的使用权,那么接下来会发生什么就没人可以预测了。
Muduo网络库的源码中几乎全部采用智能指针的方式而避免了原始指针,这或许是现代C++的编程风格。源码中需要用到了三种智能指针:shared_ptr,weak_ptr,scoped_ptr。shared_ptr是强引用,其大致用法可以通过:
最后来看一下将智能指针应用到Observer中的例子。根据之前的描述,Observable需要一个vector来保存它所依赖的Observer,那么我就保存Observer对象的弱引用,当我更新时取出该弱引用,若该引用有效,则更新之,否则从vector中将其删除,代码如下:
void Observable::notifyObservers() {
//std::vector> observers_;
for (auto iter = observers_.begin(); iter != obervers_.end(); ) {
shared_ptr obj = iter->lock(); // weak_ptr::lock(),若引用有效,返回强引用;否则返回空shared_ptr
if (obj) {
obj->update;
iter++;
} else {
iter = obervers_.erase(iter); //删除元素导致原先迭代器失效
}
}
}
shared_ptr是管理共享资源的利器,需要注意避免循环引用,通常的做法是owner持有指向child的shared_ptr,child持有指向owner的weak_ptr。有时候我们需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,此时通常用“弱回调”技术解决。所谓弱回调,就是使用weak_ptr,在回调的时候先尝试提升为shared_ptr,如果提升成功,就执行回调,否则就不执行回调。
关于回调:C/C++中的回调一般是指传入函数指针作为别的函数的参数的一种调用方式。一般回调函数由用户自己指定,然后由别人的代码在运行时调用。这样的方式极大的提高了设计的灵活性,比如C++STL中的sort函数,用户可以自定义cmp函数来定义自己的“小于”规则,而库设计人员就不必关心用户如何设计的,它只规定这个接口的形状就ok了。
Muduo网络库中采用了大量的回调,比如TcpConnection连接创建时会调用回调connectionCallback_函数,而此回调绑定到TcpServer的connectionCallback,而后者由用户调用TcpServer::setconnectioncallback()指定。另外Muduo库应用了大量的boost::bind()与boost::function()来绑定回调函数,但是目前我对这两个的用法的理解还不是很深,随着以后学习的深入再慢慢理解吧。