Потокобезопасный ленивый получить и отпустить - PullRequest
2 голосов
/ 08 октября 2010

Я столкнулся с досадной проблемой, и мне понадобится совет ...

Допустим, у меня есть несколько маленьких объектов MyObject, которые могут создавать большие объекты MyExtendedObject.MyExtendedObject имеют большой размер и потребляют ресурсы ЦП, поэтому создание выполняется лениво, и я стараюсь как можно скорее удалить их из памяти:

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_ для хранения статуса конструкции (построено / не построено) и другие биты для подсчета ссылок.Это единственный способ управлять двумя переменными (подсчет ссылок и статус строительства) одновременно с помощью атомарных операций.

Некоторые вопросы сейчас:

  • Как вы думаете, это 100% пуленепробиваемый?

  • Знаете ли вы какие-нибудь лучшие решения?

РЕДАКТИРОВАТЬ: Некоторые предложили использоватьshared_ptr.Один для рабочего решения с shared_ptr!Обратите внимание, что мне нужно: ленивая конструкция и разрушение, когда никто больше не использует ее.

Ответы [ 2 ]

1 голос
/ 08 октября 2010

Как сказал Стив, вам в основном нужен shared_ptr для части строительства / разрушения. Если вы не можете использовать boost, то я бы порекомендовал скопировать соответствующий код из заголовков boost (я полагаю, что лицензия позволяет это) или любого другого обходного пути, необходимого для обхода ваших глупых корпоративных политик. Другое преимущество этого подхода состоит в том, что когда вы можете принять TR1 или C ++ 0x, вам не нужно переписывать / поддерживать какую-либо пользовательскую реализацию, вы можете просто использовать [then] код встроенной библиотеки.

Что касается безопасности потоков (с которой Стив не обращался), я считаю, что почти всегда неплохо использовать примитивы синхронизации вместо того, чтобы пытаться сделать это самостоятельно с помощью пользовательской блокировки. Я бы предложил использовать CRITICAL_SECTION, а затем добавить некоторый временной код, чтобы убедиться, что общее время блокировки / разблокировки ничтожно мало. Хорошо выполнять много операций блокировки / разблокировки, если не слишком много конфликтов, и вам не придется отлаживать непонятные проблемы многопоточного доступа.

В любом случае, это мой совет, FWIW.

Редактирование: я должен добавить, что, как только вы эффективно используете boost, вы, вероятно, захотите сохранить weak_ptr в классе MyObject, чтобы вы могли проверить, существует ли расширенный объект в функции "get", не удерживая ссылка на это. Это позволит вашему «уничтожению по счетам», когда ни один внешний вызывающий объект все еще не использует экземпляр. Итак, ваша функция «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. Надеюсь, это понятно.

См .: Повышение уязвимости слабого_потора в многопоточной программе для реализации пула ресурсов для получения дополнительной информации о слабом_птре и безопасности потоков.

0 голосов
/ 08 октября 2010

Похоже, вы на полпути к реконструкции boost :: shared_ptr , которая предлагает подсчет ссылок объекта через инкапсулированный необработанный указатель.

В вашем случае использование будет boost::shared_ptr<MyExtendedObject>.

РЕДАКТИРОВАТЬ: Согласно комментарию @ ronag, shared_ptr теперь поддерживается изначально многими текущими компиляторами после принятия в последнюю версию языка..

РЕДАКТИРОВАТЬ: Первое построение:

shared_ptr<MyExtendedObject> master(new MyExtendedObject);

Когда последняя копия master выходит из области видимости, будет вызван delete MyExendedObject.

...