多线程下的返回值优化陷阱
来源:互联网 发布:淘宝无线店铺装修教程 编辑:程序博客网 时间:2024/06/10 05:59
“函数的返回值优化”是我们对程序的一个常见优化手段。只要可能,我们都应该返回对象的有效引用,而不是重新生成一个临时对象。但是,也许这种想法在多线程里需要更仔细的斟酌一下。
我从一个简单例子讲起:
template<T>
class FdMap
{
std::vector<T> vec_;
public:
void Set(int fd,const T & v){
if(fd >= 0){
if(fd < vec_.size()){
vec_[fd] = v;
}else{
vec_.resize(fd + 1);
vec_[fd] = v;
}
}
}
T Get(int fd) const{
if(fd >= 0 && fd < vec_.size())
return vec_[fd];
return T();
}
};
这里FdMap是一个把int映射到T对象的类型,采用std::vector作为底层容器。它所做的工作很简单:在需要的时候扩充容器vec_;如果访问超过了范围,返回T的默认值。
如果FdMap要在多线程下工作,那么我们需要加一点代码,进行必要的同步:
template<T>
class FdMap
{
std::vector<T> vec_;
Mutex mutex_; //锁同步对象
public:
void Set(int fd,const T & v){
if(fd >= 0){
Guard g(mutex_) //加锁
if(fd < vec_.size()){
vec_[fd] = v;
}else{
vec_.resize(fd + 1);
vec_[fd] = v;
}
}
}
T Get(int fd) const{
if(fd >= 0){
Guard g(mutex_) //加锁
if(fd < vec_.size()){
return vec_[fd];
}
return T();
}
};
其中Mutex是任意线程同步类型;Guard是对应的范围保卫类型,在构造的时候锁住mutex_,析构的时候解锁。到目前为止,FdMap类非常完美,没有任何问题。
在实际应用中,我们可能把FdMap用于关联socket fd和对应的客户端连接对象(例如ClientData),于是可能的实例化是FdMap<ClientData *>。
不过有经验的程序员马上会看出,直接在FdMap里存储ClientData指针是不行的。如果某个线程Get到了一个fd的ClientData指针,而另一个线程却试图关闭这个fd连接,那么ClientData对象是释放掉,还是继续有效?如果释放了ClientData对象,那么前一个线程随后的访问就会失效;如果继续保留ClientData对象,那么谁也不知道该在何时释放它了,就会内存泄漏。
正确的做法是,在FdMap里存储ClientData的智能指针,那么哪个线程都不需要特意释放ClientData对象,智能指针会在适当的时候处理。为了保险,我们决定使用boost::shared_ptr,因为大家都认为它是正确的。
OK,我们有了一个设计完美,运行正确的FdMap,完整的实例化是:
FdMap<boost::shared_ptr<ClientData> >
终于有一天,我们需要优化代码,于是我们重新审视上面的代码。Get函数!是的,它的返回值是否可以优化?因为对于智能指针,即使是拷贝构造函数都是昂贵的,能够减少一个临时对象的构造,对于我们的确太诱人了。
但是有一个明显的问题:当fd不在范围内的时候,返回谁的引用?且看下面的实现。
下面是我们的“优化”:
const T & Get(int fd) const{
static const T DEF = T();
if(fd >= 0){
Guard g(mutex_) //加锁
if(fd < vec_.size()){
return vec_[fd];
}
return DEF;
}
通过增加一个局部静态常量DEF,解决了返回默认引用的问题。如果fd在范围内,那么返回vec_里的对象引用,是没有问题的。
但是当我再次运行这个“优化版”的时候,不幸的是,一天之后程序崩溃了!怎么回事?我检查了所有用到Get函数的地方:
boost::shared_ptr<ClientData> pClient = fdMap.Get(fd);
这样的代码实在看不出有什么问题,而检查Get函数的实现,也没有发现任何问题!
事实上,问题是这样的:
仔细分析Get函数的操作,可以发现,在“return vec_[fd];”之后,FdMap内部的锁已经解开了,而此时调用线程得到的还是FdMap::vec_[fd]对象的引用,于是接下来给pClient赋值的操作就是一个没有任何保护的过程。如果在把这个引用赋值给pClient的过程中,FdMap::vec_[fd]没有被任何其他线程更改,那么一切正常;否则,程序就可能崩溃!
明白了这个例子,相信大家在以后的优化过程中,会更谨慎的处理多线程下的代码。
- 多线程下的返回值优化陷阱
- snprintf()函数返回值“陷阱”
- C++函数的返回值,你不懂得!陷阱无处不在!
- [C陷阱]getchar的返回值是int而不是char
- 多线程多核的delete陷阱
- python多线程,获取多线程的返回值
- 带返回值的多线程
- VC++多线程下内存操作的优化
- VC++多线程下内存操作的优化
- C++的返回值优化
- 编译器的返回值优化
- Java表达式的陷阱——多线程的陷阱
- 返回内部静态成员的陷阱inet_ntoa()
- 处理WinForm多线程程序时的陷阱
- 处理WinForm多线程程序时的陷阱
- java多线程编程的常见陷阱
- boost::shared_ptr的多线程使用陷阱
- Java多线程编程的常见陷阱
- back jobs
- 手动修改spfile.ora文件导致oracle启动不了的解决办法
- 梦回长安
- 东方有线项目分析设计阶段遇到的问题及总结
- MP3"磁盘错误!"解决方法总结
- 多线程下的返回值优化陷阱
- qi
- 鸭子港乡政府网站 很囧很雷人很山寨
- 实用级反主动防御rootkit设计思路
- The False Use of SVN
- 部署Oracle数据库11g提高中型企业竞争力
- TortoiseSVN -设置 - External Programs: Diff Viewer外部程序:差异查看器 - WinMerge
- ARM微处理器系列
- 计算机会议及期刊级别