当多线程下的析构遇上虚函数

今天遇到的一个教训:
有这样的一个基类
class service{
public:
    void start();
    void stop();
    virtual void svc();
 ...
    virtual ~service();
};
service::~service(){ stop();}
service是一个服务线程的基类,svc是线程的入口。在析构函数中调用stop确保线程优雅终止。有如下派生类:
class some_service : service{
public:
    virtual void svc();    
    //没有显式定义析构函数
};
结果,程序崩溃!调试结果显示,stop过程导致线程崩溃,而这个stop是在析构函数中触发的。stop的工作过程大致是:通知service退出,等待service退出,清理数据。
过程很简单,不应该有什么错误。
再查看崩溃部分的代码,发现,svc中请求一个锁对象的时候失败,失败的原因是锁句柄无效!嗯,这大概是资源管理方面的漏洞了,仔细分析代码,问题就出在服务的析构函数身上!因为svc使用了some_service的成员数据,那么,按道理,svc这个线程,必须在对象析构以前安全退出,也就是说,在~some_service()调用时就要退出。~some_service()退出前,会析构所有的成员对象。如果此时的svc还没有退出,就会导致错误。不太严格的来说,一个正常的析构过程是:派生类的析构函数-->数据成员-->基类的析构函数-->基类的数据成员。很显然,stop的调用直到~service中才会被调用,而此时some_service::svc所需要的数据对象都已经被销毁了,当然会出问题。
在回头来看,这次的错误似乎是在some_service,没有恰当的stop线程,但是根源却在于service提供了一个易错的功能,这就是好心办坏事的典型!
于是,
修改~service为{assert(!running());}
那么要不要在~some_service 中实现{if (running()) stop();}呢?如果实现,那么,some_service的派生类仍然会遇到同样问题。如果不提供这样的实现,那么,用户必须在所有必要的地方调用stop();
到目前为止,没有想到什么好办法来解决这一问题,最安全的办法就是让用户在每个必要的地方,调用stop();
总结如下:在多线程的情况下,析构函数的效果不应该对线程执行函数造成任何影响。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页