c++ - 线程安全的延迟获取和释放

标签 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 的工作解决方案!请注意,我需要:懒惰的构造和销毁,当没人再使用它时。

最佳答案

正如史蒂夫所说,您基本上需要 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/

相关文章:

c++ - 我正在使用C++,需要帮助来识别代码中的错误

c++ - Boost膨胀算法解压缩

python - 无法从 python C++ 包装器访问析构函数

java - Android - 创建线程时为假

c# - 加入线程时是否需要内存屏障?

java - Java中对 volatile 对象进行非 volatile 引用的行为

c++ - 使用 HDF5 线程安全库

c++ - 引用重新分配的变量

java - 线程和事务 : nested transactions not supported

ios - 异步调用后无法刷新 UI