C ++: параллелизм и деструкторы - PullRequest
4 голосов
/ 15 июля 2011

Предположим, у вас есть объект, который может быть принят многими потоками.Критическая секция используется для защиты чувствительных областей.Но как насчет деструктора?Даже если я вхожу в критическую секцию, как только я вхожу в деструктор, как только деструктор был вызван, объект уже признан недействительным?

Мой ход мыслей: скажем, я вхожу в деструктор, и мне приходится ждатьв критическом разделе, потому что какой-то другой поток все еще использует его.Как только он закончит, я могу закончить уничтожение объекта.Имеет ли это смысл?

Ответы [ 6 ]

3 голосов
/ 15 июля 2011

В общем, вы не должны уничтожать объект, пока не узнаете, что никакой другой поток не использует его.Период.

Рассмотрим этот сценарий на основе вашей «последовательности мыслей»:

  • Тема A: Получить ссылку на объект X
  • Тема A: Блокировать объект X
  • Поток B: получить ссылку на объект X
  • Поток B: заблокировать объект X lock
  • Поток A: разблокировать объект X
  • Поток B: заблокировать объект X;разблокировать объект X;уничтожить объект X

Теперь рассмотрим, что произойдет, если время немного отличается:

  • Поток A: Получить ссылку на объект X
  • Поток B: Получить объектСсылка X
  • Тема B: блокировка объекта X;разблокировать объект X;уничтожить объект X
  • Поток A: заблокировать объект X - падение

Короче говоря, уничтожение объекта должно быть синхронизировано где-то, кроме самого объекта.Одним из распространенных вариантов является использование подсчета ссылок.Поток A будет блокировать саму ссылку на объект, предотвращая удаление ссылки и уничтожение объекта, пока ему не удастся увеличить счетчик ссылок (поддерживая объект живым).Затем поток B просто очищает ссылку и уменьшает счетчик ссылок.Вы не можете предсказать, какой поток на самом деле вызовет деструктор, но в любом случае это будет безопасно.

Модель подсчета ссылок может быть легко реализована с помощью boost::shared_ptr или std::shared_ptr;деструктор не запустится, пока все shared_ptr во всех потоках не будут уничтожены (или направлены в другое место), поэтому в момент уничтожения вы знаете, что единственным указателем на оставшийся объект является указатель this деструкторасам по себе.

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

std::shared_ptr<SomeObject> objref;
Mutex objlock;

void ok1() {
  objlock.lock();
  objref->dosomething(); // ok; reference is locked
  objlock.unlock();
}

void ok2() {
  std::shared_ptr<SomeObject> localref;
  objlock.lock();
  localref = objref;
  objlock.unlock();

  localref->dosomething(); // ok; local reference
}

void notok1() {
  objref->dosomething(); // not ok; reference may be modified
}

void notok2() {
  std::shared_ptr<SomeObject> localref = objref; // not ok; objref may be modified
  localref->dosomething();
}

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

2 голосов
/ 15 июля 2011

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

Вы должны убедиться, что если один поток уничтожает ваши объекты, то другой поток не должен вызывать функции для этого объекта, или первый поток должен ждать, пока второй поток завершит вызов функции.

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

1 голос
/ 12 января 2017

Да, это нормально. Если класс поддерживает такое использование, клиентам не нужно синхронизировать уничтожение; то есть им не нужно следить за тем, чтобы все другие методы объекта завершили до вызова деструктора.

Я бы порекомендовал клиентам не предполагать, что они могут сделать это, если это явно не задокументировано. Клиенты по умолчанию несут такую ​​нагрузку, в частности, со стандартными объектами библиотеки (§17.6.4.10 / 2).

Есть случаи, когда это хорошо, хотя; Деструктор std::condition_variable, например, специально разрешает текущие вызовы condition_variable::wait() метода при запуске ~condition_variable(). Требуется только, чтобы клиенты не инициировали вызовы wait () после запуска ~condition_variable().

Может быть, чище потребовать, чтобы клиент синхронизировал доступ к деструктору - и конструктору в этом отношении, - как это делает большинство остальных стандартных библиотек. Я бы порекомендовал сделать это, если это возможно.

Однако существуют определенные схемы, в которых может иметь смысл освободить клиентов от бремени полной синхронизации уничтожения. Общий шаблон condition_variable выглядит примерно так: рассмотрите возможность использования объекта, который обрабатывает, возможно, долго выполняющиеся запросы. Пользователь делает следующее:

  1. Построить объект
  2. Заставить объект получать запросы от других потоков.
  3. Заставить объект перестать получать запросы: в этот момент некоторые невыполненные запросы могут выполняться, но новые не могут быть вызваны.
  4. Уничтожить объект. Деструктор будет блокироваться до тех пор, пока все запросы не будут выполнены, в противном случае текущие запросы могут иметь плохое время.

В качестве альтернативы может потребоваться, чтобы клиенты действительно синхронизировали доступ. Вы можете вообразить шаг 3.5 выше, где клиент вызывает метод shutdown() для объекта, который выполняет блокировку, после чего клиент может безопасно уничтожить объект. Однако у этой конструкции есть некоторые недостатки; это усложняет API и вводит дополнительное состояние для объекта shutdown-but-valid.

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

1 голос
/ 02 марта 2014

Да, пока вы находитесь в деструкторе, объект уже признан недействительным.

Я использовал метод Destroy (), который входит в критическую секцию, а затем сам разрушает ее.

Время жизни объектазакончился до вызова деструктора?

1 голос
/ 15 июля 2011

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

  1. Сделайте потребителей объекта дочерними, чтобы они не могли существовать вне вашего объекта, либо
  2. используйте передачу сообщений / посредник.

Если вы пойдете по последнему маршруту, я настоятельно рекомендую 0mq http://www.zeromq.org/.

1 голос
/ 15 июля 2011

Возможно, что пока один поток ожидает CS в деструкторе, другой уничтожает объект, и если CS принадлежит объекту, он также будет уничтожен. Так что это не очень хороший дизайн.

...