【问题标题】:Running method while destroying the object在销毁对象时运行方法
【发布时间】:2011-11-15 23:48:39
【问题描述】:

几天前,我的朋友告诉我他们在项目中遇到的情况。 有人决定,最好在并行线程(如异步)中销毁NotVerySafeClass 的对象。它是前一段时间实施的。 现在他们崩溃了,因为在主线程中调用了一些方法,而对象被销毁了。 创建了一些解决方法来处理这种情况。

当然,这只是一个不太好的解决方案的例子,但仍然是一个问题:

有什么方法可以防止NotVerySafeClass内部出现这种情况(如果destructor已经被调用,则拒绝运行destructor,并强制destructor等待,直到任何运行method结束(假设只有一个method))?

【问题讨论】:

  • 为什么不在调用方法后销毁对象,而不是在另一个线程中?这是一个设计缺陷,需要在设计中修复,而不是通过补丁。
  • 我们可以在析构函数启动后,在销毁尚未完成时启动该方法。问题是:我们可以防止这种情况发生吗?

标签: c++ multithreading methods destructor


【解决方案1】:

不,不,不。这是一个基本的设计问题,它显示了在考虑多线程情况和一般竞争条件时的常见误解。

有一件事同样可能发生,这确实表明您需要一个所有权概念:函数调用线程可以在对象被销毁后立即调用函数,因此不再有对象并尝试在其上调用函数是UB,由于对象不再存在,它也没有机会阻止dtor和成员函数之间的任何交互。

【讨论】:

  • 来自 err 'challenged' C++ 开发人员的查询:) 是否可以重写 NVSC 析构函数以采取一些不会导致立即释放对象的操作?也许 NVSC 实例可以放在池队列中以供以后重新使用,或者可能排队到等待一段时间后释放它的线程?
  • @MartinJames:dtor 不执行解除分配,这是由操作员 delete 完成的,理论上可以为此类重载。但这并不能解决任何问题,因为删除实例会调用 dtor,从而破坏对象。你不能对此做任何事情。之后访问它是 UB(想想改变的 vptrs,每个 dtor 自动发生的事情)。如果您的对象甚至没有更新,事情当然会变得更糟。
  • @MartinJames:您可以做的是确保任何线程仅在不再需要它之后才将其删除。正如其他人所建议的那样,在这里使用智能指针(可能是 boost::shared_ptr)是一个好主意。
  • 谢谢,@PlasmaHH。请放心,我不会很快尝试这个:)
  • 即使没有多线程,这种行为也是可重现的。想想 VC++,使用 Windows 消息。如果您有一个很长的等待循环(等待套接字或其他东西),那么您将在等待循环内进行消息泵送。如果现在出现一条消息,这会导致“this”对象的破坏(即对话框已关闭),而不是在您退出 DispatchMessage 调用时拥有无效的对象。
【解决方案2】:

您需要的是完善的所有权政策。为什么代码在仍然需要的时候销毁对象

如果没有有关代码的更多详细信息,std::shared_ptr 可能会解决此问题。根据您的具体情况,您或许可以使用更轻量级的策略来解决它。

【讨论】:

  • 我不会抛出shared_ptr nilly-willy。通过定义数据的明确所有者,大多数情况都可以(并且应该)在没有共享指针的大锤的情况下解决。
  • @Matthieu:对。我们需要更多地了解被销毁的对象以及使用它来决定的代码。
  • 确实,没有灵丹妙药:)
【解决方案3】:

听起来像一个可怕的设计。难道你不能使用智能指针来确保对象只有在没有人持有对它的任何引用时才被销毁?

如果没有,我会使用一些外部同步机制。将析构函数与方法同步真的很尴尬。

【讨论】:

    【解决方案4】:

    没有任何方法可以用来防止这种情况发生。

    在多线程编程中,您需要确保如果有其他线程仍在访问该对象,该对象不会被删除。

    如果您正在处理此类代码,需要进行根本性修复

    【讨论】:

      【解决方案5】:

      (不是宣传糟糕的设计)而是回答您的两个问题:

      ...如果已经调用了析构函数,则拒绝运行方法

      您可以使用@snemarch 和@Simon(锁)提出的解决方案来做到这一点。要处理一个线程在析构函数内,而另一个线程在方法开始时等待锁定的情况,您需要在线程之间共享的内存中以线程安全的方式跟踪对象的状态。例如。在释放锁之前由析构函数设置为 0 的静态原子 int。该方法在获取锁后检查 int,如果为 0,则退出。

      ...强制析构函数等待,直到任何正在运行的方法结束

      @snemarch 和@Simon(锁)提出的解决方案可以解决这个问题。

      【讨论】:

        【解决方案6】:

        没有。只需要适当地设计程序,使其是线程安全的。

        【讨论】:

          【解决方案7】:

          为什么不使用互斥体/信号量?在任何方法的开始,互斥锁被锁定,析构函数等待直到互斥锁被解锁。这是一个修复,而不是解决方案。也许您应该更改应用程序的一部分的设计。

          【讨论】:

          • 方法可能在析构函数启动后启动。
          • 然后我投票赞成 shared_ptr 建议(加上重构您的代码)。
          【解决方案8】:

          简单的回答:没有。

          稍微长一点的答案:你可以用互斥锁来保护你类中的每个成员函数和析构函数......欢迎来到死锁机会和性能噩梦。

          召集一群暴徒,将一些设计感打造成认为并行破坏是个好主意的“人”:)

          【讨论】:

            猜你喜欢
            • 2022-11-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多