【问题标题】:Proper way to stop a thread from a destructor从析构函数中停止线程的正确方法
【发布时间】:2012-08-06 12:12:32
【问题描述】:

我有一个类,它有一个Start 方法来启动一个线程,该线程以预定义的时间间隔执行虚拟ThreadFunctionStop 方法设置一个事件并等待线程终止(通过线程句柄上的WaitForSingleObject)。

MyThread的析构函数中,我调用了Stop方法。因此,每当我删除实例时,我确信线程在删除返回之前已停止。

class MyThread
{
    void Start();
    void Stop();
    ~MyThread() { Stop(); }
    virtual VOID ThreadFunction() { }
};

接下来我有一个派生自MyThread的类:

class A : MyThread
{
    virtual VOID ThreadFunction()
    {
        for (int i = 0; i < 1000; i++)
            TestFunction();
    }

    void TestFunction() { // Do something }
};

考虑这段代码:

A a = new A();
a->Start();
delete a;

问题是delete a会先调用A的析构函数,然后再调用MyThread的析构函数,对吧?因此,如果线程正在执行ThreadFunction 中的for-loop,则Stop 方法将在a 被破坏后调用。当ThreadFunction 在已破坏的实例上调用TestFunction 时,这可能会导致访问冲突。

一种解决方案是向class A 添加一个调用Stop 方法的析构函数,如下所示:

class A : MyThread
{
    ~A()
   {
       Stop();
   }
}

但是因为我有一个更复杂的类层次结构,它涉及多个继承的类,这意味着我必须在每个析构函数中调用 Stop 方法,这将导致 Stop 方法被调用很多次,而只有一个需要删除的实例。

还有其他方法可以解决这个问题吗?

【问题讨论】:

  • 在删除类之前显式调用清理函数(在你的情况下停止),不方便,但我就是这样做的。我总是尽量在析构函数中放尽可能少的代码,只是因为你描述了析构函数的工作原理
  • 这可能是大多数人不关心类似 Java 的线程类的原因。这个类有不止一个职责:它管理线程的生命周期它执行的代码。把这两个关注点分开,一切都会变得容易。
  • 实际上,MyThread won't be called at all since it's not virtual. If it was, then it would work fine since while the destructors are called the actual memory haven't been released yet, so e.g. this` 的析构函数在调用 A 析构函数的情况下仍然有效。不过您必须小心,因为成员变量可能已被释放和破坏,因此在调用 A 析构函数后访问它们可能会导致问题。
  • @JoachimPileborg:析构函数按照我在问题中描述的顺序调用,至少这是我的调试器在单步执行代码时告诉我的。并且析构函数没有定义为virtual

标签: c++ destructor


【解决方案1】:

MyThread 中的析构函数应定义为“虚拟”。

class A{
public:
    A(){cout<<"A"<<endl;}
    virtual ~A(){cout<<"~A"<<endl;}
};

class B : public A{ 
public:
    B(){cout<<"B"<<endl;}
    ~B(){cout<<"~B"<<endl;}
};

int main(){
    A* b = new B();
    cout<<"do something"<<endl;
    delete b;
    b = NULL;
    return 0;
}

结果是: 一种 乙 做一点事 ~B ~A

当它不使用虚拟时,结果是: 一种 乙 做一点事 ~A

【讨论】:

  • 这对 OP 要求的问题没有帮助,即数据成员可能已被删除
  • 我单步执行了代码,首先进入A的析构函数,最后进入MyThread的析构函数。而且它们没有被定义为virtual。所以你说的和我看到的不一样。
  • 只有当你通过指向MyThread的指针销毁它时,虚拟的析构函数才重要。由于代码使用了指向A 的指针,因此没有问题(您仍应将其设为虚拟以避免该问题)。
  • 正确的建议,错误的理由。如果在基类指针上调用 delete,那么使用非虚拟析构函数是一个问题。
  • @aasa:我看到你已经修改了你的答案。我同意你的观点,如果我这样做,我将需要一个虚拟析构函数:delete (MyThread *)a;,但我没有这样做,这真的不能解决我的问题 :) 无论如何感谢你的努力!
【解决方案2】:

正如 Rolle 和 R. Martinho Fernandes 建议的那样,我需要将这两个问题分开。

class MyThread 不应启动或停止自身,因为其责任应仅限于其执行的代码,而不是线程的生命周期。

所以解决方案是从另一个负责线程生命周期的类(启动线程的同一类)中停止线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-07
    • 2019-08-09
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 2010-09-26
    相关资源
    最近更新 更多