Какую политику shared_ptr использовать с асинхронной схемой? - PullRequest
2 голосов
/ 21 ноября 2011

У меня есть собственный многопоточный сервис, который обрабатывает некоторые команды.Сервис состоит из анализатора команд, рабочих потоков с очередями и некоторых кешей.Я не хочу следить за жизненным циклом каждого объекта, поэтому я использую очень обширный shared_ptr.Каждый компонент использует shared_ptr по-своему:

  • анализатор команд создает shared_ptr и сохраняет их в кеше;
  • работник привязывает shared_ptr к функторам и помещает их в очередь.
  • кэш временно или постоянно содержит некоторый shared_ptr.
  • данные, на которые ссылается shared_ptr, могут также содержать некоторые другие shared_ptr.

И есть другая базовая служба (например, приемник команди отправитель), который имеет ту же структуру, но использует свой собственный кеш, рабочие и shared_ptr.Он независим от моего сервиса и поддерживается другим разработчиком.

Это полный кошмар, когда я пытаюсь отследить все зависимости shared_ptr для предотвращения перекрестных ссылок.

Есть ли способ указать некоторыеshared_ptr "interface" или "policy", так что я буду знать, какой shared_ptr я могу безопасно передать базовому сервису без проверки кода или взаимодействия с разработчиком?Политика должна включать цикл владения shared_ptr, например, рабочий удерживает функтор с binded shared_ptr после вызова функции dispatch () и только до вызова некоторой другой функции, в то время как кеш содержит shared_ptr с момента вызова конструктора кеша и до неговызов деструктора.

Особенно мне любопытно, что происходит остановка, когда приложение может зависнуть при ожидании присоединения потоков.

Ответы [ 2 ]

1 голос
/ 21 ноября 2011

Серебряной пули нет ... и shared_ptr определенно не одна.


Мой первый вопрос: вам нужны все эти общие указатели?

Лучший способ избежать циклических ссылок - определить политику жизни каждого объекта и убедиться, что они совместимы.Это может быть легко задокументировано:

  • вы передаете мне ссылку, я ожидаю, что объект будет жить в течение вызова функции, но не более
  • вы передаете мне unique_ptr, ятеперь я отвечаю за объект
  • вы передаете мне shared_ptr, я рассчитываю, что смогу самостоятельно держать объект в руках, не оказывая негативного влияния на вас

СейчасЕсть редкие ситуации, когда использование shared_ptr действительно необходимо.Обозначение кэшей заставляет меня думать, что это может быть ваш случай, по крайней мере, для некоторых применений.

В этом случае вы можете (по крайней мере неофициально) применять многоуровневый подход.

  • Определить количество слоев, от 0 (базовое) до бесконечного
  • Каждый тип объекта приписывается слою, несколько типов могут совместно использоватьтот же слой
  • Объект типа A может содержать shared_ptr только для объекта типа B только в том случае, если Layer(A) > Layer(B)

Примечание.что мы прямо запрещаем родственные отношения.При такой схеме невозможно сформировать круг ссылок.Действительно, мы получаем DAG (направленный ациклический граф).

Теперь, когда тип создается, ему должен быть присвоен номер слоя, и это должно быть задокументировано (предпочтительно в коде).

Объект может изменить слоя, однако:

  • , если номер его слоя уменьшается, то вы должны пересмотреть ссылки, которые он содержит (легко)
  • , еслиномер его слоя увеличивается, тогда вы должны пересмотреть все ссылки на него (жесткие)

Примечание: условно, типы объектов, которые не могут содержать никаких ссылок, обычно находятся в слое 0.

Примечание 2: Сначала я наткнулся на это соглашение в статье Херба Саттера, где он применил его к мьютексам и попытался предотвратить тупик.Это адаптация к текущей проблеме.


Это может быть применено немного более автоматически (компилятором), если вы готовы работать с существующей кодовой базой.

Мы создаем новый класс SharedPtr, осведомленный о нашей схеме размещения:

template <typename T>
constexpr unsigned getLayer(T const&) { return T::Layer; }


template <typename T, unsigned L>
class SharedPtrImpl {
public:
  explicit SharedPtrImpl(T* t): _p(t)
  {
    static_assert(L > getLayer(std::declval<T>()), "Layering Violation");
  }

  T* get() const { return _p.get(); }

  T& operator*() const { return *this->get(); }
  T* operator->() const { return this->get(); }

private:
  std::shared_ptr<T> _p;
};

Каждому типу, который может содержаться в таком SharedPtr, присваивается свой слой статически, и мы используем базовый класс, чтобы помочь намout:

template <unsigned L>
struct LayerMember {
  static unsigned const Layer = L;

  template <typename T>
  using SharedPtr<T> = SharedPtrImpl<T, L>;
};

И теперь мы можем легко использовать его:

class Foo: public LayerMember<3> {
public:

private:
  SharedPtr<Bar> _bar; // statically checked!
};

Однако этот подход к кодированию немного более сложен, я думаю, что соглашение вполне может быть достаточным;)

0 голосов
/ 21 ноября 2011

Вы должны посмотреть на weak_ptr.Он дополняет shared_ptr, но не сохраняет объекты живыми, поэтому очень полезен, когда у вас могут быть циклические ссылки.

...