C++ 析构函数应该是线程安全的吗?

C++ 析构函数应该是线程安全的吗?,c++,multithreading,destructor,C++,Multithreading,Destructor,我正在浏览一段遗留代码,发现以下代码片段: MyClass::~MyClass() { EnterCriticalSection(&cs); //Access Data Members, **NO Global** members are being accessed here LeaveCriticalSection(&cs); } 我想知道有没有可能保护毁灭者 考虑一个场景: 1. Thread1 - About to execute any of the

我正在浏览一段遗留代码,发现以下代码片段:

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,那么它可能会工作。但如果顺序颠倒了呢


这是设计问题吗?

当对象正在使用时,不应调用析构函数。如果您正在处理这种情况,它需要一个基本的解决方案。但是,析构函数可能需要修改其他内容(与被析构函数的类无关),并且可能需要一个关键部分(例如,递减全局计数器)。

我认为您有一个更基本的问题。当另一个线程仍在调用成员函数时,销毁一个线程上的对象是不合法的。这本身就是错误的

即使您成功地用关键部分保护析构函数,当另一个线程开始执行函数的其余部分时会发生什么?它将对一个已删除的对象执行此操作,该对象(取决于它的分配位置)将是垃圾内存或简单的无效对象

您需要修改代码以确保对象在仍在使用时不会被破坏

定义“线程安全”。这可能是现代计算中两个最难理解的词


但是,如果有可能从两个不同的线程输入两次析构函数(正如使用symchronization对象所暗示的那样),那么您的代码就陷入了困境。正在删除您询问的对象的对象应该管理此操作-同步应该(可能)在该级别进行

不会有什么不同。如您所说,如果调用顺序颠倒,那么您正在对一个已破坏的对象调用一个成员函数,这将失败。同步无法修复该逻辑错误(对于初学者,成员函数调用将尝试获取已破坏的锁对象)。

如果要访问全局变量,可能需要线程安全,是的

我的“Window”类将自己添加到构造函数中的“knownWindows”列表中,并在析构函数中删除自己。“knownWindows”需要是线程安全的,这样它们在执行时都会锁定互斥锁


另一方面,如果析构函数只访问被销毁对象的成员,则会出现设计问题。

我支持尼尔·巴特沃斯的评论。当然,负责删除和访问myclass的实体应该对此进行检查


实际上,这种同步将从创建MyClass类型的对象的那一刻开始。

我已经看到了ACE线程的一种情况,其中线程在ACE_任务_基本对象上运行,对象从另一个线程中销毁。析构函数在等待条件之前获取锁并通知包含的线程。在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()的线程退出之前不会销毁对象。

您的评论说“这里没有访问全局成员”,所以我猜不会。只有创建对象的线程才应该销毁它,那么您将从其他哪个线程保护它呢

我自己喜欢有序地创建和销毁,只有一个对象拥有另一个子对象,而引用该子对象的任何其他对象都是树中较低位置的后代。如果这些子对象中的任何一个子对象代表不同的线程,那么它们将确保在树上进行销毁之前完成

例如:

  • main()创建一个对象
    • 对象A包含对象B
      • 对象B包含对象C
        • 对象C创建一个访问对象a和B的线程
        • 对象C的析构函数运行,等待其线程完成
      • 对象B的析构函数运行
    • 对象A的析构函数运行
  • main()返回
对象A和B的析构函数根本不需要考虑线程,而对象C的析构函数只需要实现一些通信机制(例如,等待事件)和它选择自己创建的线程


如果您开始将对象的引用(指针)分发给任意线程,而不跟踪这些线程的创建和销毁时间,则可能会遇到麻烦,但如果您这样做,则应该使用引用计数,如果这样做,则调用析构函数时为时已晚。如果仍然存在对对象的引用,那么就不应该有人试图调用它的析构函数。

老问题,但仍然有效

通常,一个类的公共成员改变了一个关键部分,可以从不同的线程访问该部分,应该锁定这个关键部分。但物体的破坏是包括临界截面在内的物体状态的最终改变

所以,如果有异步操作在进行,在进入对象的这个临界状态时,销毁肯定应该等到那个临界部分再次离开。一种方法是在析构函数中使用锁。当然,这无助于保证以后不会再错误地访问对象本身


但这种技术可以用于同步。在异步结束时销毁对象。在临界段上操作。

但它本身是否应该是线程安全的?一个类是否有可能同时被“销毁两次”?@TomášZato:没有。如果一个类(带有一个非平凡的析构函数)被“销毁两次”,这与是否以线程安全的方式发生无关——它在任何情况下都是UB