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

时间:2009-03-14 18:30:08

标签: 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 member function which uses critical section
2. Thread2-  About to execute destructor.

如果执行顺序为1 => 2则可能有效。但如果订单被撤销怎么办?

这是一个设计问题吗?

9 个答案:

答案 0 :(得分:34)

在使用对象时,析构函数应不被调用。如果您正在处理这种情况,需要一个基本的修复。但是,析构函数可能想要改变其他一些东西(与被破坏的类无关),它可能需要一个关键部分(例如减少全局计数器)。

答案 1 :(得分:4)

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

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

您需要更改代码以确保在使用时不会破坏对象。

答案 2 :(得分:4)

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

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

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

答案 3 :(得分:3)

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

但是如果有可能从两个不同的线程中输入两次析构函数(因为使用了symchronisation对象意味着),那么你的代码就是深入的doo-doo。删除您要询问的对象的对象应该是管理它 - (可能)在该级别上应该进行同步。

答案 4 :(得分:3)

我见过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()的线程退出之前不会销毁该对象。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

我是Neil ButterWorth的评论。当然,负责删除和访问myclass的实体应该对此进行检查。

此同步将从创建MyClass类型的对象开始实际开始。

答案 7 :(得分:0)

您的评论说“没有全球成员在这里被访问”所以我猜不会。只有创建对象的线程才能销毁它,所以从其他线程中你会保护它吗?

我喜欢自己有序的创建和破坏,其中只有一个对象拥有另一个子对象,并且引用该子对象的任何其他对象是树中更下方的后代。如果这些子对象中的任何一个代表不同的线程,那么它们将确保在销毁之前完成。

示例:

  • main()创建对象A.
    • 对象A包含对象B.
      • 对象B包含对象C.
        • object C创建一个访问对象A和B的线程
        • 对象C的析构函数运行,等待其线程完成
      • 对象B的析构函数运行
    • 对象A的析构函数运行
  • main()返回

对象A和B的析构函数根本不需要考虑线程,而对象C的析构函数只需要用它选择创建自己的线程来实现一些通信机制(例如,等待一个事件)。

如果你开始将对象的引用(指针)分配给任意线程而不跟踪创建和销毁这些线程的时间,你只会遇到麻烦,但是如果你这样做那么你应该使用引用计数如果你是,那么在调用析构函数时就已经太晚了。如果仍有对象的引用,那么任何人都不应该尝试调用它的析构函数。

答案 8 :(得分:0)

旧问题,但仍然有效,恕我直言。

通常,类的公共成员更改了关键部分,可以从不同的线程访问这些关键部分。但是销毁物件是物件包括关键部分的状态的最终改变。

因此,如果正在进行异步操作,在此状态下输入了对象的临界状态,则销毁操作一定要等到再次留下该临界区。一种方法是在析构函数中使用锁定。当然,这无助于确保以后不会再错误地访问对象本身。

但是该技术可以用于同步。以异步结束破坏对象。在关键部分进行操作。