我遇到了一个烦人的问题,需要一些建议...
假设我有一堆小的 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 的工作解决方案!请注意,我需要:懒惰的构造和销毁,当没人再使用它时。
最佳答案
正如史蒂夫所说,您基本上需要 shared_ptr 用于构建/销毁部分。如果您不能使用 boost,那么我建议您从 boost header 中复制适当的代码(我相信许可证允许这样做),或者您需要的任何其他解决方法来规避愚蠢的公司政策。这种方法的另一个优点是当你可以采用 TR1 或 C++0x 时,你不需要重写/维护任何自定义实现,你可以只使用 [then] 内置库代码。
至于线程安全(Steve 没有解决),我发现使用同步原语几乎总是一个好主意,而不是尝试通过自定义锁定自己正确处理。我建议使用 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 的引用计数自动完成的。希望这很清楚。
参见:Boost weak_ptr's in a multi-threaded program to implement a resource pool有关 weak_ptr 和线程安全的更多信息。
关于c++ - 线程安全的延迟获取和释放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3891866/