线程安全的懒惰获取和释放

时间:2010-10-08 15:09:08

标签: c++ multithreading thread-safety lazy-evaluation

我遇到了一个烦人的问题,需要一些建议......

假设我有一堆小的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获得工作解决方案!请注意,我需要:懒惰的构造和破坏,当没有人不再使用它时。

2 个答案:

答案 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