2010-10-08 46 views
2

我运行到kindof一个恼人的问题,并会需要一些建议...线程安全懒get和释放

比方说,我有一堆小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(); 
    } 
} 

几点说明:

  • 我不能使用升压。我想但实在不行。

  • 我只使用CompareExchange和Incr/Decr。不要问。

  • 我使用ref_的第一个位来存储构造状态(构造/未构造)和其他位来进行参考计数。这是我发现通过原子操作同时管理2个变量(参考计数和施工状态)的唯一途径。

现在的一些问题:

  • 你认为这是100%的防弹?

  • 你知道一些更好的解决方案吗?

编辑:有人建议使用shared_ptr。一个与shared_ptr工作解决方案!请注意,我需要:懒惰建设和破坏时,没有人不再使用它。

+0

没有必要将'Extended'拼写为'Extented'或'Extanted',更不用说在同一个程序中。 – 2010-10-08 15:13:24

+0

谢谢,我试图纠正它。英语不是我的母语:) – Julio 2010-10-08 15:27:18

回答

1

正如史蒂夫说的,你基本上想要shared_ptr的建设/销毁部分。如果你不能使用boost,那么我建议从boost头文件中复制适当的代码(我相信许可证允许这样做),或者你需要的其他解决方法来绕过你愚蠢的公司策略。这种方法的另一个优点是,当你可以采用TR1或C++ 0x时,你不需要重写/维护任何定制的实现,你可以使用[然后]内置的库代码。

至于线程安全性(史蒂夫没有提到),我发现使用同步原语几乎总是一个好主意,而不是试图通过自定义锁定来让它自己正确。我建议使用CRITICAL_SECTION,然后添加一些时间码以确保总锁定/解锁时间可以忽略不计。只要没有太多的争用,就可以进行大量的锁定/解锁操作,而且您不必调试模糊的线程访问问题。

这是我的建议,无论如何,FWIW。

编辑:我应该补充说,一旦你有效地使用boost,你可能会想要在MyObject类中保留一个weak_ptr,所以你可以检查扩展对象是否仍然存在于“get”对它的引用。当没有外部调用者仍在使用实例时,这将允许你的“ref counting destruction”。所以,你的“获取”功能看起来像:

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和线程安全性的更多信息。

+0

感谢您的回答!但正如我所说,我仍在等待shared_ptr完全满足我需求的解决方案。并且为了您的信息,我的锁解锁机制通过关键部分实现。 – Julio 2010-10-08 16:38:41

+0

对于您的编辑,确定如此,它应该可以工作,并且我在早期阶段就已经接近这个解决方案了:对于每个GetExtentedObject调用,您仍然拥有完全锁定。我的最终目的是在更新引用计数时完全摆脱锁定。 – Julio 2010-10-08 17:01:49

+0

我想看到这个证明与时间输出/等。在我没有使用它之前。你可以自由地尝试进一步优化和/或尝试推出你自己的无锁风格实现(正如我所说,我不会推荐),但是如果你的锁定时间与对象初始化相比是不可忽略的和其他操作,也许你有其他设计问题。 – Nick 2010-10-08 17:58:01

0

听起来好像你正在重建boost::shared_ptr,它通过封装的原始指针提供对象的引用计数。

在你的情况下使用将是boost::shared_ptr<MyExtendedObject>

编辑:Per @ ronag的评论,shared_ptr现在被许多当前的编译器在被接受到最新版本的语言后本地支持。

编辑:第一次建设:

shared_ptr<MyExtendedObject> master(new MyExtendedObject); 

master最后一个副本超出范围,delete MyExendedObject将被调用。

+0

是的,我怀疑这一点。假设我不能使用提升。我想但我真的不能。你知道,大公司,奇怪的政策等...... – Julio 2010-10-08 15:23:49

+1

@Julio - Boost的这部分内容仅包含标题。下载该标题并重新使用您需要的代码。 – 2010-10-08 15:25:10

+2

你也有std :: tr1 :: shared_ptr或std :: shared_ptr。 – ronag 2010-10-08 16:05:29