2011年(17)
分类: C/C++
2011-06-22 15:47:50
最近忙着将开源的三维图形引擎OSG封装成ActiveX控件,陆续遭遇了一些问题,今天将其中比较有趣的一个记录在此。不光是因为该问题涉及到非常经典的Meyers单件的应用局限,还有一个原因在于问题虽然得到解决,但是我对于其个中因果并不了然。记录的目的也是作为提醒。路漫漫匹夫怎敢懈怠。
控件封装好之后,嵌入IE没有问题,嵌入C#窗体时如果加载osgEarth数据则会在析构时出现异常,体现为访问内存冲突。加载控件源码调试之后错误定位在下面一行:
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(s_engineNodeCacheMutex);
EngineNodeCache::iterator k = getEngineNodeCache().find( uid );
if (k != getEngineNodeCache().end())
{
getEngineNodeCache().erase(k);
OE_DEBUG << LC << "Unregistered engine " << uid << std::endl;
}
很容易知道在这种情况下getEngineNodeCache()返回了空集合,进而产生了内存访问异常错误。但是当我简单的在find函数之前加了非空保护之后,却遇到了另外一个让我大跌眼镜的问题,程序出现了内存泄露。看来这个空集合的出现不正常的。进一步看了osgEarth的相关定义,发现存在一个Meyers单件。
static
EngineNodeCache& getEngineNodeCache()
{
static EngineNodeCache s_cache;
return s_cache;
}
从代码看不出问题,而且进一步的调试也证明在桌面程序和IE里该单件都能够正常析构,仅在C#窗体中会出现错误。困惑之余注意到在C#窗体应用时于加锁一步要等待一定时间,而前两者则几乎立刻执行了。难道这里存在一个线程同步或者说涉及到了Meyers模式的多线程应用了?联想到以前读过Imferfect C++中关于meyers单件线程安全的描述,似乎这里存在一个疑点。
假设1、在C#的窗体中,出于某些原因,发生了并发的getEngineNodeCache线程,那么的确有可能出现死亡对话问题,会有多于一个静态EngineNodeCache 被创建,那么如果析构的时候访问了错误的对象,从而产生这个问题。那么是什么原因让C#窗体中产生了原本不存在的并发?
假设2、如果没有产生并发getEngineNodeCache,理论上静态对象会在main函数之后进行析构,那么mian函数结束之前EngineNod析构时,s_cache会返回空值就不好理解了。这里并没有静态成员间的依赖,析构时不应出现此类问题。是否在C#中调用COM会产生某种特别的内存管理方式,这种方式打乱了原本C++中的惯例?是否C#对于栈上存在的静态成员也进行了统一处理?遗憾的是我对C#的掌握还达不到这样的程度。无从追查下去。
最终通过将Meyers单件模式修改成双向加锁的单件顺利的解决了这个问题,但是我仍然不能证实前面的两个疑问。