我遇到了一个烦人的问题,需要一些建议......
假设我有一堆小的MyObject,可以构造更大的MyExtendedObject。 MyExtendedObject很大且占用CPU,所以构造很懒,我尝试尽快将它们从内存中删除:
MyExtendedObject * MyObject::GetExtentedObject(){
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
}
++ref_;
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
if(0 == (--ref_))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
}
扩展对象仅在开头构造一次,并在最后一个调用者释放时销毁。请注意,有些可能不止一次构建,但这不是问题。
现在,这绝对不是线程安全的,所以我做了一个“天真”的线程安全实现:
MyExtendedObject * MyObject::GetExtentedObject(){
Lock();
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
}
++ref_;
Unlock();
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
Lock();
if(0 == (--ref_))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
Unlock();
}
这是更好的,但现在我花了一些不可忽视的时间锁定和解锁......
我感觉我们只有在建造或毁坏时才能支付锁定/解锁费用。
我提出了这个解决方案:
MyExtendedObject * MyObject::GetExtentedObject(){
long addref = InterlockedCompareExchange(&ref_, 0, 0);
long result;
do{
result = addref + 2;
} while ((result-2) != (addref = InterlockedCompareExchange(&ref_, result, addref)));
if(0 == (result&1)){
Lock();
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
InterlockedIncrement(&ref_);
}
Unlock();
}
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
long release = InterlockedCompareExchange(&ref_, 0, 0);
long result = 0;
do{
result = release - 2;
} while ((result+2) != (release = InterlockedCompareExchange(&ref_, result, release)));
if(1 == result)
{
Lock();
if(1 == InterlockedCompareExchange((long*)&ref_, 0, 1))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
Unlock();
}
}
一些解释:
我无法使用Boost。我想但是真的不能。
我故意只使用CompareExchange和Incr / Decr。不要问。
我使用ref_的第一位存储构造状态(构造/未构造)和其他位用于引用计数。这是我通过原子操作同时管理2个变量(引用计数和构造状态)的唯一方法。
现在有些问题:
你认为它是100%防弹吗?
您知道更好的解决方案吗?
编辑:有人建议使用shared_ptr。使用shared_ptr获得工作解决方案!请注意,我需要:懒惰的构造和破坏,当没有人不再使用它时。
答案 0 :(得分:1)
正如Steve所说,你基本上需要shared_ptr来构建/破坏部分。如果您不能使用boost,那么我建议您从boost标头中复制相应的代码(我相信许可证允许这样做),或者您需要采取的任何其他解决方法来规避您的愚蠢的公司政策。这种方法的另一个优点是,当你可以采用TR1或C ++ 0x时,你不需要重写/维护任何自定义实现,你可以只使用[then]内置库代码。
至于线程安全(史蒂夫没有解决),我发现使用同步原语几乎总是一个好主意,而不是试图通过自定义锁定来实现它。我建议使用CRITICAL_SECTION,然后添加一些计时代码以确保总锁定/解锁时间可以忽略不计。只要没有太多的争用,就可以进行大量的锁定/解锁操作,并且您不必调试模糊的线程访问问题。
这是我的建议,无论如何,FWIW。
编辑:我应该补充一点,一旦你有效地使用了boost,你可能想要在MyObject类中保留一个weak_ptr,这样你就可以检查扩展对象是否仍然存在于“get”函数中而不保持参考它。当没有外部调用者仍在使用实例时,这将允许您“重新计算销毁”。因此,您的“获取”功能如下所示:
shared_ptr< MyExtendedObject > MyObject::GetExtentedObject(){
RIIALock lock( my_CCriticalSection_instance );
shared_ptr< MyExtendedObject > spObject = my_weak_ptr.lock();
if (spObject) { return spObject; }
shared_ptr< MyExtendedObject > spObject = make_shared< MyExtendedObject >();
my_weak_ptr = spObject;
return spObject;
}
...并且您不需要释放函数,因为该部分是通过shared_ptr的引用计数自动完成的。希望很清楚。
有关weak_ptr和线程安全的详细信息,请参阅:Boost weak_ptr's in a multi-threaded program to implement a resource pool。
答案 1 :(得分:0)
听起来你正在重建boost::shared_ptr,它通过封装的原始指针提供对象的引用计数。
在您的情况下,使用情况为boost::shared_ptr<MyExtendedObject>
。
编辑:Per @ ronag的评论,shared_ptr
现在被许多当前的编译器本地支持,并被接受为最新版本的语言。
编辑:第一次施工:
shared_ptr<MyExtendedObject> master(new MyExtendedObject);
当master
的最后一个副本超出范围时,将调用delete MyExendedObject
。