Должны ли деструкторы быть потокобезопасными?

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

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, то это может сработать. Но что, если порядок отменен?

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

Ответы

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

Я видел случай с потоками 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().

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

Пример:

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

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

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