一种破坏“线程”类的方法

时间:2009-10-06 18:53:56

标签: c++ multithreading

这是我的线程类的骨架:

class MyThread {
public:
   virutal ~MyThread();

   // will start thread with svc() as thread entry point
   void start() = 0;        

   // derive class will specialize what the thread should do
   virtual void svc() = 0;                
};

代码中的某处我创建了MyThread的实例,后来我想要销毁它。 在这种情况下,MyThread~MyThread()被调用。 MyThread:svc()仍在运行并使用对象的数据成员。因此,在继续使用析构函数之前,我需要礼貌地通知MyThread:svc()停止旋转。

销毁线程对象的可接受方法是什么?

注意:我正在寻找与平台无关的解决方案。

UPD:很明显,问题的根源在于C ++对象代表线程和OS线程之间没有关系。所以问题是:在对象归属的上下文中,是否有一种可接受的方法可以使线程对象像普通的C ++对象一样运行,或者它应该被视为一个不寻常的对象(例如,我们应该在抛弃它之前调用join()吗?

8 个答案:

答案 0 :(得分:7)

考虑到您的附加要求,作为对Checkers回复的评论(即 最简单的方法):

我同意由于各种原因加入DTor是有问题的。但是从那开始,线程对象的生命周期与OS线程对象的生命周期无关。


首先,您需要将线程使用的数据与线程对象本身分开。它们是具有不同生命周期要求的不同实体。

一种方法是重新安装数据,并且任何想要访问它的线程都拥有对数据的强引用。这样,任何线程都不会突然陷入虚空,但是一旦没有人接触到它,数据就会被破坏。


第二,关于线程加入时被破坏的线程对象:
我不确定这是不是一个好主意。线程对象通常是一种查询线程状态的方法 - 但是一旦线程完成,线程对象就会死掉,没有人可以告诉你线程完成了。

通常,我完全将线程对象的生命周期与OS线程的生命周期分离:销毁线程对象不应该影响线程本身。我看到两种基本方法:

  1. 线程句柄对象 - 由线程创建者返回的引用再次计数,可以在不影响OS线程的情况下尽早释放。它将公开JoinIsFinished等方法,并可以访问线程共享数据。
  2. (如果线程对象保持相关的执行状态,则threafFunc本身可以保存对它的引用,从而确保在线程结束之前不会释放实例)

    1. Thin Wrapper - 您只需在OS线程句柄周围创建一个临时文件。您无法轻松保持线程的其他状态,但它可能足以使其工作:在任何地方,您都可以将OS线程句柄转换为线程对象。大多数通信 - 例如告诉线程终止 - 将通过共享数据。

    2. 对于您的代码示例,这意味着:将start()svc()

      分开

      你大致使用这个API(XxxxPtr可以是例如boost :: shared_ptr):

      class Thread
      {
         public:
           bool IsFinished();
           void Join();
           bool TryJoin(long timeout); 
      
           WorkerPtr GetWorker();
      
           static ThreadPtr Start(WorkerPtr worker); // creates the thread
      };
      
      
      class Worker
      {
      private:
         virtual void Svc() = 0;
      
         friend class Thread; // so thread can run Svc()
      }
      

      Worker可以包含一个ThreadPtr本身,保证在执行Svc()期间线程对象存在。如果允许多个线程处理相同的数据,则必须是线程列表。否则,Thread::Start必须拒绝已经与线程关联的Worker。


      动机:如何处理阻止的流氓线程? 假设一个线程因某种原因未能在一段时间内终止,即使你告诉它。你只有三个选择:

      • 死锁,你的应用程序挂起。如果你加入了析构函数,通常会发生这种情况。
      • 猛烈终止线程。这可能是暴力终止该应用程序。
      • 让线程在其自己的数据上运行完成 - 您可以通知用户,谁可以安全地保存&出口。或者你只是让流氓线程在它自己的数据副本上跳舞(不再由主线程引用)直到它完成。

答案 1 :(得分:4)

通常任何特定于操作系统的线程API都允许您“加入”一个线程。也就是说,在线程句柄上无限期地阻塞,直到线程函数返回。

所以,

  1. 通知线程函数返回(例如,通过在其循环中将标志设置为false)。
  2. 在尝试删除线程对象
  3. 之前,加入线程以确保实际线程终止。
  4. 然后你可以继续销毁线程对象(你也可以加入析构函数,尽管有些人反对阻止析构函数。)。
  5. 我之前有一个类似的“线程工作者”类和相应的“工作项”类(a-la Java的ThreadRunnable,但线程没有终止但等待对于要执行的新Runnable对象。)

    最后,如果你加入一个单独的“shutdown”函数或析构函数,没有区别,除了一个单独的函数更清楚。

    1. 如果您加入析构函数并且线程阻塞,您将无限期地等待。
    2. 如果您加入一个单独的函数并且线程阻塞,您将无限期地等待。
    3. 如果您分离线程并让它自行完成,它通常会阻止应用程序退出,因此您将无限期地等待。
    4. 因此,没有直接的方法可以使线程像常规C ++对象一样运行并忽略其OS线程语义,除非您可以保证您的线程代码在收到通知时几乎可以立即终止。

答案 2 :(得分:1)

你可以在你的svc方法中使用这样的东西

while (alive){ //loops}
//free resources after while.

在析构函数中,您可以将alive成员设置为false。或者,你可以有一个pleaseDie()方法,它将alive成员设置为false,并且可以从外部调用,请求Thread实例停止处理。

void
Thread::pleaseDie()
{

 this->alive = false;
}

答案 3 :(得分:1)

首先需要一种与线程通信的方式来告诉它关闭。执行此操作的最佳机制取决于svc()正在执行的操作。例如,如果它在消息队列上循环,则可以在该队列中插入“请停止”消息。否则,您可以简单地添加一个成员bool变量(并同步对它的访问),该变量由svc()定期检查,并由想要销毁该对象的线程设置。您可以向基类添加一个纯虚拟stop()函数,为实现者提供一个明确的信号,即它必须实现svc()以使其类“runnable”,来实现stop()使它“停止”。

在要求线程停止后,必须等到它退出才能销毁对象。同样,有几种方法可以做到这一点。一种是使stop()函数阻塞。例如,它可以等待“ok,我现在真的停止了”条件变量由运行svc()的线程设置。或者,调用者可以在运行svc()的线程上“等待”。 “等待”的方式取决于平台。

答案 4 :(得分:1)

大多数线程系统允许您向thead发送信号。

示例:pthreads

pthread_kill(pthread_t thread, int sig);

这将向线程发送一个signall。 你可以用它来杀死线程。虽然这可能会使一些资源处于未定义的状态。

资源问题的解决方案是安装一个signall处理程序 因此,当调用信号处理程序时,它会抛出异常。这将导致线程堆栈展开到入口点,然后您可以获取线程来检查有关天气活动的变量。

注意:你永远不应该允许异常从一个线程传播(这是如此不确定,我的眼睛流血思考它)。基本上在线程入口点捕获异常然后检查一些状态变量以查看线程是否应该真正退出。

同时发送信号的线程应该通过连接等待线程死亡。

唯一的问题是,当你抛弃信号处理函数时,你需要小心。您不应该使用异步信号(即可能由另一个线程中的信号生成的信号)。一个好用的是SIGSEGV。如果这种情况正常发生,那么你已经访问过任何线程应该考虑退出的无效内存!

您可能还需要在某些系统上指定一个额外的标志来应对 See This article

使用pthreads的工作示例:

#include <pthread.h>
#include <iostream>

extern "C" void* startThread(void*);
extern "C" void  shouldIexit(int sig);

class Thread
{
    public:
        Thread();
        virtual ~Thread();
    private:
        friend void* startThread(void*);

        void start();
        virtual void run() = 0;

        bool        running;
        pthread_t   thread;
};


// I have seen a lot of implementations use a static class method to do this.
// DON'T. It is not portable. This is because the C++ ABI is not defined.
//
// It currently works on several compilers but will break if these compilers
// change the ABI they use. To gurantee this to work you should use a
// function that is declared as extern "C" this guarantees that the ABI is 
// correct for the callback. (Note this is true for all C callback functions)
void* startThread(void* data)
{
    Thread* thread  = reinterpret_cast<Thread*>(data);
    thread->start();
}
void shouldIexit(int sig)
{
    // You should not use std::cout in signal handler.
    // This is for Demo purposes only.
    std::cout << "Signal" << std::endl;

    signal(sig,shouldIexit);
    // The default handler would kill the thread.
    // But by returning you can continue your code where you left off.
    // Or by throwing you can cause the stack to unwind (if the exception is caught).
    // If you do not catch the exception it is implementation defined weather the
    // stack is unwound.
    throw int(3);  // use int for simplicity in demo
}


Thread::Thread()
    :running(true)
{
    // Note starting the thread in the constructor means that the thread may
    // start before the derived classes constructor finishes. This may potentially
    // be a problem. It is started here to make the code succinct and the derived
    // class used has no constructor so it does not matter.
    if (pthread_create(&thread,NULL,startThread,this) != 0)
    {
        throw int(5); // use int for simplicity in demo.
    }
}

Thread::~Thread()
{
    void*   ignore;

    running = false;
    pthread_kill(thread,SIGSEGV); // Tell thread it may want to exit.
    pthread_join(thread,&ignore); // Wait for it to finish.

    // Do NOT leave before thread has exited.

    std::cout << "Thread Object Destroyed" << std::endl;
}

void Thread::start()
{
    while(running)
    {
        try
        {
            this->run();
        }
        catch(...)
        {}
    }
    std::cout << "Thread exiting" << std::endl;
}
class MyTestThread:public Thread
{
    public:
        virtual void run()
        {
            // Unless the signal causes an exception
            // this loop will never exit.
            while(true)
            {
                sleep(5);
            }
        }

};

struct Info
{
     Info() {std::cout << "Info" << std::endl;}
    ~Info() {std::cout << "Done: The thread Should have exited before this" << std::endl;}
};

int main()
{
    signal(SIGSEGV,shouldIexit);

    Info                info;
    MyTestThread        test;

    sleep(4);
    std::cout << "Exiting About to Exit" << std::endl;

}


> ./a.exe
Info
Exiting About to Exit
Signal
Thread exiting
Thread Object Destroyed
Done: The thread Should have exited before this
>

答案 5 :(得分:0)

你应该添加专用的线程管理类(即MyThreadMngr),它处理这个和其他任务,比如簿记,拥有线程句柄等。线程本身应该以某种方式向线程管理器发出信号,告知它将终止它和MyThreadMngr应该即像汤姆提出的那样有一个循环。

这样的线程管理器类可能会有更多的操作。

答案 6 :(得分:0)

我认为最简单的方法是将线程执行代码包装在循环中

while(isRunning())
{
     ... thread implementation ...
}

您也可以通过执行特定调用来停止线程,例如,当您使用WIN32线程时,可以在析构函数的线程句柄上调用TerminateThread。

答案 7 :(得分:0)

我给出了一个简单而干净的设计,没有信号,没有同步,没有需要杀死。

根据您的MyThread,我建议重命名并添加如下:

class MyThread { 
public: 
   virutal ~MyThread(); 

   // will be called when starting a thread, 
   // could do some initial operations 
   virtual bool OnStart() = 0;  

   // will be called when stopping a thread, say calling join().
   virtual bool OnStop() = 0;

   // derive class will specialize what the thread should do, 
   // say the thread loop such as 
   // while (bRunning) {
   //    do the job.
   // } 
   virtual int OnRun() = 0;                 
}; 

线程接口用户将控制MyThread的生命周期。

实际上真正的线程对象如下:

    class IThread
    {
    public:
        virtual API ~IThread() {}

        /* The real destructor. */
        virtual void Destroy(void) = 0;

        /* Starts this thread, it will call MyThread::OnStart() 
             * and then call MyThread::OnRun() just after created 
         *   the thread. */
        virtual bool Start(void) = 0;

        /* Stops a thread. will call MyThread::OnStop(). */
        virtual void Stop(void) = 0;

        /* If Wait() called, thread won't call MyThread::OnStop().
         * If could, it returns the value of MyThread::OnRun()
         *   returned */
        virtual int Wait(void) = 0;

        /* your staff */
        virtual MyThread * Command(void) = 0;

    };

/* The interface to create a thread */
extern IThread * ThrdCreate(MyThread *p);

参见完整的界面

http://effoaddon.googlecode.com/svn/trunk/devel/effo/codebase/addons/thrd/include/thrd_i.h

编码示例

案例1.受控线程循环

class ThreadLoop : public MyThread
{
private:
   bool m_bRunning;
public:
   virtual bool OnStart() { m_bRunning = true; }  

   virtual bool OnStop() { m_bRunning = false; }

   virtual int OnRun() 
   {
         while (m_bRunning) {
              do your job;
         }
   }                 
};

int main(int argc, char **argv)
{
      ThreadLoop oLoop;

      IThread *pThread = ThrdCreate(&oLoop);
      // Start the thread, it will call Loop::OnStart() 
      //and then call Loop::OnRun() internally.
      pThread->Start();
      do your things here. when it is time to stop the thread, call stop().
      // Stop the thread, it will call Loop::OnStop(), 
      // so Loop::OnRun() will go to the end
      pThread->Stop();
      // done, destroy the thread
      pThread->Destroy();
}

案例2.不知道线程何时停止

class ThreadLoop : public MyThread
{
public:
   virtual bool OnStart() {  }  

   virtual bool OnStop() { }

   virtual int OnRun() 
   {
         do your job until finish.
   }                 
};

int main(int argc, char **argv)
{
      ThreadLoop oLoop;

      IThread *pThread = ThrdCreate(&oLoop);
      // Start the thread, it will call Loop::OnStart() 
      //and then call Loop::OnRun() internally.
      pThread->Start();
      do your things here. Since you don't know when the job will 
      finish in the thread loop. call wait().
      // Wait the thread, it doesn't call Loop::OnStop()
      pThread->Wait();
      // done, destroy the thread
      pThread->Destroy();
}

完整的IThread实施:

http://effoaddon.googlecode.com/svn/trunk/devel/effo/codebase/addons/thrd/src/thrd/thrd.cpp