Синхронизация в деструкторе: почему бы и нет?Как? - PullRequest
0 голосов
/ 10 декабря 2018

Или ... как правильно сочетать параллелизм, RAII и полиморфизм?

Это очень практичный вопрос.Мы были укушены этой комбинацией, которая описана как ужасающая ошибка Чендлера Каррута (при отметке 1:18:45)!

Если вам нравятся ошибки, попробуйте поймать загадку здесь (адаптировано из Chandler'stalk):

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

class A {
 public:
  virtual void F() = 0;
  void Done() {
    std::lock_guard<std::mutex> l{m};
    is_done = true;
    cv.notify_one();
    std::cout << "Called Done..." << std::endl;
  }
  virtual ~A() {
    std::unique_lock<std::mutex> l{m};
    std::cout << "Waiting for Done..." << std::endl;
    cv.wait(l, [&] {return is_done;});
    std::cout << "Destroying object..." << std::endl;
  }

 private:
  std::mutex m;
  std::condition_variable cv;
  bool is_done{false};
};

class B: public A {
 public:
  virtual void F() {}
  ~B() {}
};

int main() {
  A *obj{new B{}};

  std::thread t1{[=] {
    obj->F();
    obj->Done();
  }};

  delete obj;
  t1.join();

  return 0;
}

Проблема (замеченная при компиляции через clang++ -fsanatize=thread) сводится к гонке между чтением виртуальной таблицы (полиморфизм) и записью на нее (перед входом в ~ А).Запись выполняется как часть цепочки уничтожения (поэтому в деструкторе A не вызывается метод из B).

Рекомендуемый обходной путь - переместить синхронизацию за пределы деструктора, заставляя каждого клиента класса вызыватьWaitUntilDone / метод Join.Это легко забыть, и именно поэтому мы в первую очередь хотели использовать идиому RAII.

Таким образом, мои вопросы таковы:

  • Есть ли хороший способ обеспечить синхронизациюв базовом деструкторе?
  • Из любопытства, почему на земле виртуальный стол даже используется из деструктора?Я бы ожидал статическое связывание здесь.

1 Ответ

0 голосов
/ 17 декабря 2018

Вы можете использовать оболочку RAII для объектов, которые не могут напрямую поддерживать семантику RAII.Конструктор объекта-обертки может создать внутренний объект, а затем сделать все, что логически является частью конструкции, но может быть сделано только после возврата конструктора внутреннего объекта.Точно так же деструктор объекта-обертки может делать все, что нужно сделать, прежде чем деструктор внутреннего объекта может быть безопасно вызван, а затем он может уничтожить внутренний объект.

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