线程安全懒get和释放
我运行到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工作解决方案!请注意,我需要:懒惰建设和破坏时,没有人不再使用它。
正如史蒂夫说的,你基本上想要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和线程安全性的更多信息。
感谢您的回答!但正如我所说,我仍在等待shared_ptr完全满足我需求的解决方案。并且为了您的信息,我的锁解锁机制通过关键部分实现。 – Julio 2010-10-08 16:38:41
对于您的编辑,确定如此,它应该可以工作,并且我在早期阶段就已经接近这个解决方案了:对于每个GetExtentedObject调用,您仍然拥有完全锁定。我的最终目的是在更新引用计数时完全摆脱锁定。 – Julio 2010-10-08 17:01:49
我想看到这个证明与时间输出/等。在我没有使用它之前。你可以自由地尝试进一步优化和/或尝试推出你自己的无锁风格实现(正如我所说,我不会推荐),但是如果你的锁定时间与对象初始化相比是不可忽略的和其他操作,也许你有其他设计问题。 – Nick 2010-10-08 17:58:01
听起来好像你正在重建boost::shared_ptr,它通过封装的原始指针提供对象的引用计数。
在你的情况下使用将是boost::shared_ptr<MyExtendedObject>
。
编辑:Per @ ronag的评论,shared_ptr
现在被许多当前的编译器在被接受到最新版本的语言后本地支持。
编辑:第一次建设:
shared_ptr<MyExtendedObject> master(new MyExtendedObject);
当master
最后一个副本超出范围,delete MyExendedObject
将被调用。
没有必要将'Extended'拼写为'Extented'或'Extanted',更不用说在同一个程序中。 – 2010-10-08 15:13:24
谢谢,我试图纠正它。英语不是我的母语:) – Julio 2010-10-08 15:27:18