防止派生类被销毁

时间:2013-12-20 23:00:36

标签: c++ multithreading inheritance

我有一个带有受保护构造函数的基类IRunnable,因此不能创建基类的实例。该类有一个纯虚方法run(),它必须由派生类实现。我将IRunnable *的指针传递给某种类似java的Executor,它控制许多线程,并将这些Runnables分配给它们。 我的问题是,当这样一个指向IRunnable派生类的对象的IRunnable *指针(位于另一个Thread的堆栈上)被分配给一个工作线程时,我无法确定派生的Object是不是在工作线程仍在使用它时销毁,因为在其线程上具有该对象的线程可能会离开创建该对象的范围。 例如:

int main(void)
{
    CExecutor myExecutor(...); 
    for (UInt32 i = 0; i < 2; i++)
    {
        derivedRunnable myRunnable1; //will be destroyed after each loop iteration
        myExecutor.submit(myRunnable1);
    }
}

class derivedRunnable : public IRunnable
{
public:
    derivedRunnable(const char * name = NULL) : IRunnable(name) {}
    ~derivedRunnable() {}
    void run(void)
    {
        for (UInt32 i = 0; i < 100; i++)
        {
            char name [256] = {"\0"};

            pthread_getname_np(pthread_self(), name, 255);
            printf("%s in step %d\n", name, i);
        }
        fflush(stdout);
    }
};

我在基类IRunnable中实现了一个引用计数,并且我在析构函数中执行了一个阻塞调用,它只会在最后一个使用它的线程取消注册时返回。问题是,派生类的析构函数首先被调用,因此在调用阻塞调用的基类被破坏之前,对象将被部分破坏。 在上面的例子中,我得到以下运行时错误:
pure virtual method called
terminate called without an active exception 如果我在.submit()调用之后插入一些usec的usleep,它将起作用,因为线程将在runnable被破坏之前完成

class IRunnable
{
    friend class CThread;
    friend class CExecutor;
private:
    CMutex mutx;
    CBinarySemaphore sem;
    UInt32 userCount;
    [...]
    virtual void run(void) = 0;
    IRunnable(const IRunnable & rhs); //deny
    IRunnable & operator= (const IRunnable & rhs); //deny

    void registerUser(void)
    {
        mutx.take(true);
        if (0 == userCount++)
            sem.take(true);
        mutx.give();
    }

    void unregisterUser(void)
    {
        mutx.take(true);
        if (0 == --userCount)
            sem.give();
        mutx.give();
    }

protected:
    IRunnable(const char * n = NULL)
    :mutx(true,false)
    ,sem(true,false)
    ,userCount(0)
    {
        setName(n);
    }

    ~IRunnable()
    {
        sem.take(true);
    }
    [...]

我该怎么办?

3 个答案:

答案 0 :(得分:2)

如果你在析构函数中阻塞,那么你的提交循环将不是异步的,这是多线程的重点。生命周期必须超过分配范围。为此,您应该动态分配IRunnable对象,并让Executor获得所有权。当作业完成时,执行人将负责删除它。

更好的是,为此接口使用共享指针。这样调用线程仍然可以引用该对象(例如检查完成)。完成的最后一个线程将删除该对象。

编辑:添加示例代码:

for (UInt32 i = 0; i < 2; i++)
{
    shared_ptr< derivedRunnable > myRunnable1(new derivedRunnable);
    myExecutor.submit(myRunnable1);
}

或保存任务:

list < shared_ptr < derivedRunnable > > jobs;
for (UInt32 i = 0; i < 2; i++)
{
    shared_ptr< derivedRunnable > myRunnable(new derivedRunnable);
    myExecutor.submit(myRunnable);
    jobs.push_back(myRunnable);
}
// do other stuff
// call a status function that you haven't written yet:
jobs.front()->WaitForCompletion();

顺便说一句,您还应该考虑切换到std :: thread或boost :: thread,具体取决于编译器的年份。

答案 1 :(得分:2)

你的问题是它不清楚对象的所有权。从您的示例中可以看出,对象属于该线程,因此您应该传递所有权,并且线程应该销毁它。为此,IRunnable析构函数必须是公共的。实际上没有理由不公开。

啊!并且应该动态创建对象:

derivedRunnable *myRunnable1 = new derivedRunnable();
myExecutor.submit(myRunnable1);

然后submit函数接收指针并将其传递给线程。当线程完成runnable时,它将破坏它:

void threadfunc(IRunnable *runnable)
{
    //run it
    delete runnable;
}

还有更多的软化解决方案,但这是最简单的。如果C ++ 11是一个选项,我最喜欢的是使用std::unique_ptr<IRunnable>。这样,对象的破坏是自动的。或者,如果您需要在主线程中保留runnable,则可以使用std::shard_ptr<IRunnable>:自动销毁和共享所有权。

答案 2 :(得分:1)

IRunnable需要有一个虚拟析构函数,以便以正确的顺序调用正确的析构函数。