析构函数中的同步:为什么不呢?怎么办?

问题描述 投票:0回答:1

或者...如何正确结合并发、RAII 和多态性?

这是一个非常实际的问题。我们被这个组合所困扰,总结为Chandler Carruth 的可怕 bug(标记为 1:18:45)!

如果你喜欢 bug,请尝试在这里抓住谜题(改编自 Chandler 的演讲):

#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
编译时发现)归结为虚拟表的读取(多态性)和对其上的 write(在进入 ~A 之前)之间的竞争。写入是作为销毁链的一部分完成的(因此在 A 的析构函数中不会调用 B 中的方法)。

建议的解决方法是将同步移到析构函数之外,强制类的每个客户端调用 WaitUntilDone/Join 方法。这很容易被忘记,这正是我们首先想要使用 RAII 习惯用法的原因。

因此,我的问题是:

  • 有没有一种好的方法可以在基本析构函数中强制同步?
  • 出于好奇,为什么在析构函数中使用虚拟表?我希望这里有静态绑定。
c++ concurrency
1个回答
0
投票

© www.soinside.com 2019 - 2024. All rights reserved.