17

我正在浏览旧代码并找到以下代码段:

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,那么它可能会起作用。但是如果顺序颠倒了怎么办?

是设计问题吗?

4

9 回答 9

37

使用对象时不应调用析构函数。如果您正在处理这种情况,则需要进行根本的修复。然而,析构函数可能想要改变一些其他的东西(这与被破坏的类无关)并且它可能需要一个临界区(例如递减一个全局计数器)。

于 2009-03-14T18:32:20.027 回答
5

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

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

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

于 2009-03-14T18:33:25.077 回答
4

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

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

另一方面,如果你的析构函数只访问被销毁对象的成员,那么你就有了设计问题。

于 2009-03-14T18:46:12.097 回答
4

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

于 2009-03-15T00:08:06.940 回答
3

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

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

于 2009-03-14T18:45:14.020 回答
0

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

于 2009-03-14T18:45:23.487 回答
0

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

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

于 2009-03-14T19:17:27.157 回答
0

您的评论说“这里没有访问全球成员”所以我猜不是。只有创建对象的线程才能销毁它,那么您将从哪个线程保护它?

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

例子:

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

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

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

于 2010-01-28T23:16:14.167 回答
0

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

一般来说,改变临界区的类的公共成员,可以从不同的线程访问,应该锁定这个临界区。但是对象的破坏是对象状态的最终改变,包括临界区。

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

但该技术可用于同步。在异步结束时销毁对象。临界区的操作。

于 2020-11-13T16:35:03.387 回答