Должны ли деструкторы быть потокобезопасными? - PullRequest
15 голосов
/ 14 марта 2009

Я просматривал устаревший код и нашел следующий фрагмент:

MyClass::~MyClass()
{
   EnterCriticalSection(&cs);

//Access Data Members, **NO Global** members are being accessed here


  LeaveCriticalSection(&cs);
}

Мне интересно, поможет ли это хоть как-то защитить деструктора?

Рассмотрим сценарий:

1. Thread1 - About to execute any of the member function which uses critical section
2. Thread2-  About to execute destructor.

Если порядок выполнения 1 => 2, то это может сработать. Но что, если заказ будет отменен?

Это проблема дизайна?

Ответы [ 8 ]

34 голосов
/ 14 марта 2009

Деструктор не должен вызываться, когда используется объект . Если вы имеете дело с такой ситуацией, необходимо фундаментальное исправление . Однако деструктор может захотеть изменить что-то другое (что не связано с уничтожаемым классом), и ему может потребоваться критическая секция (например, например, уменьшение счетчика global ).

4 голосов
/ 14 марта 2009

Если вы обращаетесь к глобальным переменным, вам может потребоваться безопасность потоков, да

например. Мой класс "Window" добавляет себя в список "knownWindows" в конструкторе и удаляет себя в деструкторе. «knownWindows» должен быть ориентирован на многопоточность, чтобы они оба блокировали мьютекс, пока они это делают.

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

4 голосов
/ 14 марта 2009

Я думаю, у вас есть более фундаментальная проблема. Не должно быть законным уничтожать ваш объект в одном потоке, пока другой поток все еще вызывает функции-члены. Это само по себе неправильно.

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

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

3 голосов
/ 15 марта 2009

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

class PeriodicThread : public ACE_Task_Base
{
public:
   PeriodicThread() : exit_( false ), mutex_()
   {
   }
   ~PeriodicThread()
   {
      mutex_.acquire();
      exit_ = true;
      mutex_.release();
      wait(); // wait for running thread to exit
   }
   int svc()
   {
      mutex_.acquire();
      while ( !exit_ ) { 
         mutex_.release();
         // perform periodic operation
         mutex_.acquire();
      }
      mutex_.release();
   }
private:
   bool exit_;
   ACE_Thread_Mutex mutex_;
};

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

3 голосов
/ 14 марта 2009

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

Но если существует вероятность того, что деструктор будет введен дважды из двух разных потоков (как предполагает использование объектов симхронизации), ваш код находится в глубоком ду-ду. Объекты, которые удаляют объект, о котором вы спрашиваете, должны управлять этим - именно (вероятно) на этом уровне должна происходить синхронизация.

0 голосов
/ 29 января 2010

В ваших комментариях написано " НЕТ глобальных членов, которые доступны здесь", так что я бы не догадался. Только поток, создавший объект, должен уничтожить его, поэтому от какого другого потока вы будете его защищать?

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

Пример:

  • main () создать объект A
    • объект A содержит объект B
      • объект B содержит объект C
        • объект C создает поток, который обращается к объектам A и B
        • Деструктор объекта C запускается, ожидая завершения потока
      • Деструктор объекта B запускается
    • Деструктор объекта А работает
  • main () возвращает

Деструкторам для объектов A и B вообще не нужно думать о потоках, а деструктору объекта C нужно только реализовать некоторый механизм связи (например, ожидание события) с потоком, который он выбрал для создания сам.

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

0 голосов
/ 14 марта 2009

Я второй комментарий от Нила ButterWorth. Абсолютно, субъекты, ответственные за удаление и доступ к myclass, должны иметь проверку на это.

Эта синхронизация начнется фактически с момента создания объекта типа MyClass.

0 голосов
/ 14 марта 2009

Не будет иметь значения. Если, как вы говорите, порядок вызовов обратный, то вы вызываете функцию-член для разрушенного объекта, и это не удастся. Синхронизация не может исправить эту логическую ошибку (для начала вызов функции-члена попытался бы получить разрушенный объект блокировки).

...