“返回值优化”是我们对个常见优化手段只要可能我们都应该返回对象有效引用而不是重新生成个临时对象但是也许这种想法在多线程里需要更仔细斟酌下
我从个简单例子讲起:
template<T>
FdMap
{
std::vector<T> vec_;
public:
void Set( fd,const T & v){
(fd >= 0){
(fd < vec_.size){
vec_[fd] = v;
}{
vec_.resize(fd + 1);
vec_[fd] = v;
}
}
}
T Get( fd) const{
(fd >= 0 && fd < vec_.size)
vec_[fd];
T;
}
};
这里FdMap是个把映射到T对象类型采用std::vector作为底层容器它所做工作很简单:在需要时候扩充容器vec_;如果访问超过了范围返回T默认值
如果FdMap要在多线程下工作那么我们需要加点代码进行必要同步:
template<T>
FdMap
{
std::vector<T> vec_;
Mutex mutex_; //锁同步对象
public:
void Set( fd,const T & v){
(fd >= 0){
Guard g(mutex_) //加锁
(fd < vec_.size){
vec_[fd] = v;
}{
vec_.resize(fd + 1);
vec_[fd] = v;
}
}
}
T Get( fd) const{
(fd >= 0){
Guard g(mutex_) //加锁
(fd < vec_.size){
vec_[fd];
}
T;
}
};
其中Mutex是任意线程同步类型;Guard是对应范围保卫类型在构造时候锁住mutex_析构时候解锁到目前为止FdMap类非常完美没有任何问题
在实际应用中我们可能把FdMap用于关联 fd和对应客户端连接对象(例如ClientData)于是可能例子化是FdMap<ClientData *>
不过有经验员马上会看出直接在FdMap里存储ClientData指针是不行如果某个线程Get到了个fdClientData指针而另个线程却试图关闭这个fd连接那么ClientData对象是释放掉还是继续有效?如果释放了ClientData对象那么前个线程随后访问就会失效;如果继续保留ClientData对象那么谁也不知道该在何时释放它了就会内存泄漏
正确做法是在FdMap里存储ClientData智能指针那么哪个线程都不需要特意释放ClientData对象智能指针会在适当时候处理为了保险我们决定使用boost::shared_ptr大家都认为它是正确
OK我们有了个设计完美运行正确FdMap完整例子化是:
FdMap<boost::shared_ptr<ClientData> >
终于有天我们需要优化代码于是我们重新审视上面代码Get!是它返回值是否可以优化?对于智能指针即使是拷贝构造都是昂贵能够减少个临时对象构造对于我们确太诱人了
但是有个明显问题:当fd不在范围内时候返回谁引用?且看下面实现
下面是我们“优化”:
const T & Get( fd) const{
const T DEF = T;
(fd >= 0){
Guard g(mutex_) //加锁
(fd < vec_.size){
vec_[fd];
}
DEF;
}
通过增加个局部静态常量DEF解决了返回默认引用问题如果fd在范围内那么返回vec_里对象引用是没有问题
但是当我再次运行这个“优化版”时候不幸是天的后崩溃了!如何回事?我检查了所有用到Get地方:
boost::shared_ptr<ClientData> pClient = fdMap.Get(fd);
这样代码实在看不出有什么问题而检查Get实现也没有发现任何问题!
事实上问题是这样:
仔细分析Get操作可以发现在“ vec_[fd];”的后FdMap内部锁已经解开了而此时线程得到还是FdMap::vec_[fd]对象引用于是接下来给pClient赋值操作就是个没有任何保护过程如果在把这个引用赋值给pClient过程中FdMap::vec_[fd]没有被任何其他线程更改那么切正常;否则就可能崩溃!
明白了这个例子相信大家在以后优化过程中会更谨慎处理多线程下代码
最新评论