2009-10-06 98 views
3

这里是我的线程类的骨架:的一种方式摧毁“线程”类

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:很明显,问题的根源是,有℃之间没有关系++对象表示线程和OS线程。所以,问题是:在对象destuction的情况下,有没有办法让线程对象像普通的C++对象能够接受的方式还是应该把它作为一个不寻常的(例如,我们应该调用join()方法destoying之前进行治疗

+1

你或许应该让DSTR虚拟,只要你想这个类是派生自。 – chollida 2009-10-06 20:43:28

+0

你是对的,更正:) – dimba 2009-10-06 21:08:54

+0

你对我的答案的评论是不准确的。我已更新以使其更清楚。请重新阅读。 – 2009-10-06 23:38:47

回答

7

考虑您的附加要求张贴到跳棋回复意见(这是做到这一点的 最直接的方式):

我同意加盟析构函数是因各种原因存在问题。但是,线程对象的生命周期与OS线程对象的生命周期无关。


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

一种方法是对数据进行重新计算,并让任何想要访问的线程都对数据有强烈的参考。这样,没有线程会突然陷入虚空,但只要没有人接触到它,数据就会被销毁。


其次,有关线程对象的时候,螺纹连接被破坏:
我不知道这是否是一个好主意。线程对象通常是查询线程状态的一种方式 - 但是一旦线程完成就会死掉的线程对象,没有人能告诉你线程已完成。

一般来说,我会完全分离线程对象的生命周期与操作系统线程的生命周期:销毁线程对象不应该影响线程本身。我看到两种基本方法:

  1. 线程处理对象 - 线程创建者返回的再次计算的引用可以在不影响操作系统线程的情况下尽快发布。它会暴露诸如Join,IsFinished等方法,并且可以授予访问线程共享数据的权限。

(如果线程对象持有相关的执行状态,threafFunc本身可以保持它的一个引用,从而保证了实例的线程结束之前将不会被释放)

  • Thin Wrapper - 您只需在OS线程句柄周围创建临时文件。你不能容易地为线程保留额外的状态,但它可能足以让它工作:在任何地方,你都可以将一个OS线程句柄变成一个线程对象。大部分的交流 - 例如告诉线程终止 - 将通过共享数据。

  • 为了您的代码示例,这意味着:独立于svc()

    start()你会大致与此API工作(XxxxPtr可能是如提高:: 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将不得不拒绝已经与线程关联的工人。


    动机:做什么用的阻断流氓线程?
    假设线程由于某种原因未能及时终止,即使您告诉它。你只有三个选择:

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

    我不确定我完全理解你的答案。是可能的,在你的例子线程将被摧毁,而关联的工人不是? 很多情况下,数据不会在几个线程之间共享,对于这些情况,您的解决方案可能过于冗长。 – dimba 2009-10-06 20:32:31

    +1

    +1,用于将线程对象与其上运行的函数使用的数据分开的建议 – 2009-10-06 21:19:17

    +0

    正确。我添加了一些“动机”段落。请不要混淆我的答复的冗长度和必要代码的冗长度。 在请求操作的线程和执行它的线程之间总是有一些*数据共享。我的关键是,一个*线程*与它所操作的* data *是一个不同的实体,并且线程启动器和线程本身都不会单方面决定销毁数据。 – peterchen 2009-10-06 21:22:15

    1

    你可以havee财产以后像这样在您的SVC方法

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

    在你的析构函数,你可以在活着的成员设置为false。或者,你可以有一个pleaseDie()方法,该活的成员设置为false,并且可以从外部请求的线程实例停止处理被调用。

    void 
    Thread::pleaseDie() 
    { 
    
    this->alive = false; 
    } 
    
    +1

    仅在d-tor中设置标志恕我直言并不能解决问题,因为当s-c仍在运行时,我很可能会在d-tor完全/部分执行的情况下运行。你的第二个建议 - 在调用Thread :: pleaseDie()之后,我不能破坏线程对象,因为我将再次运行相同的问题。 – dimba 2009-10-06 19:25:24

    +0

    这就是为什么你需要peterchen的解决方案:从操作系统级别线程分离C++线程对象。 – jmucchiello 2009-10-06 20:56:00

    4

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

    所以,

    1. 信号线程函数到(例如,通过在其环路中设置标志来false)返回。
    2. 加入线程,以确保实际线程终止试图删除线程对象之前。
    3. 然后你可以继续破坏线程对象(你也可以加入析构函数,虽然有些人反对阻止析构函数)。

    我有一个项目之前,类似的“线工人”级和相应的“工作项”级(A-LA Java的ThreadRunnable,除了线程不会终止,而是等待一个新的Runnable对象被执行)。

    最终,没有什么区别,如果你在一个单独的“关机”功能,或在析构函数加入,除了一个单独的函数是一个比较明显的。

    1. 如果你加入一个析构函数和一个线程块,你将无限期地等待。
    2. 如果你加入一个单独的函数和一个线程块,你将无限期地等待。
    3. 如果您将线程分离并让它自行完成,它通常会阻止应用程序退出,因此您将无限期地等待。

    所以有使线程的行为像一个普通的C++对象,而忽略它的操作系统线程的语义,除非你能保证你的线程代码几乎可以立即终止通知这样做时,没有直接的方法。

    +0

    这就是我试图避免的事情,加入了d-tor。它看起来很奇怪并且不直观。然而,在这种情况下,与外部逻辑相反,线程对象的逻辑是独立的(当连接返回时,会破坏对象)。 – dimba 2009-10-06 19:31:53

    +2

    在线程对象上创建一个方法,类似'shutdown'。此方法将'run'标志设置为false并加入该线程。 现在线程已停止,并且可以重新启动或删除。 – gnud 2009-10-06 19:37:22

    0

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

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

    1

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

    请求线程停止后,必须等待它退出后再销毁该对象。再次,有几种方法可以做到这一点。一种是阻止stop()函数。例如,它可以等待一个“确定,我真的停止”条件变量,由运行svc()的线程设置。或者,调用者可以在运行svc()的线程上“等待”。 “等待”的方式取决于平台。

    +0

    我喜欢那个被阻止的函数是一个普通的类方法而不是d-tor。这种解决方案的优点是我不像普通的C++对象那样处理线程对象 - 我需要在抛弃对象之前调用stop()函数。 – dimba 2009-10-06 19:38:18

    +0

    请注意,如果线程函数有任何机会访问线程类的子类的数据成员或虚拟方法,那么您从线程类的析构函数中调用stop()将不足以保证安全性。将在子类的成员值被销毁后的一段时间内出现争用情况,但在〜thread()调用stop()之前,线程仍在运行,这会对您造成问题。 – 2009-10-07 03:13:51

    1

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

    例子:并行线程

    pthread_kill(pthread_t thread, int sig); 
    

    这会signall发送到线程。 你可以用它来杀死线程。尽管这可能会使一些资源处于未定义状态。

    解决资源问题的方法是安装信号处理程序。
    因此,当调用信号处理程序时,它会引发异常。这将导致线程堆栈展开到入口点,然后您可以获取线程以检查它是否存在的天气变量。

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

    与此同时,发送信号的线程应通过加入等待线程死亡。

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

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

    工作示例使用并行线程:

    #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 
    > 
    
    +0

    我认为,当你使用信号发送线程时,你立即终止任何这些 - > run(),并因此让线程的数据处于未定义状态,这是不好的(参见方法1 ddj.com/architect/207100682)。也许如果你从你的解决方案中忽略了信号,并且在线程函数(Thread :: startup())中将Thread :: running标志与d-tor中的pthread_join()一起使用,这也将解决问题,正如一些海报在这里已经提出的那样。在这种情况下,您将在进入下一次迭代的同时离开线程函数。 – dimba 2009-10-06 20:56:19

    +0

    @idimba:你部分正确。如果你没有默认安装一个信号处理程序,线程就会终止。这不太好,将按照DDJ文章中的描述以及我上面的初步评论进行操作。但是如果你定义了你自己的信号处理程序,线程不会自动终止,直到处理程序。如果你查看代码,你会看到我安装了一个引发异常的信号处理程序。这将导致堆栈正确放开(因为异常被捕获)。只要你写出异常安全的RAII代码,这种方法是可以接受的。 – 2009-10-06 22:34:42

    +0

    你对RAII代码是正确的,这是异常安全的,这将工作。我也喜欢在你的解决方案中,用户提供的线程函数没有暴露给实现细节 - 它只是旋转:)而恕我直言,强制线程函数使用RAII数据并不总是现实的。编写exceptioon安全代码对于avarage程序员来说甚至是一个更大的挑战。 如果我的评论不正确,我会删除它:) – dimba 2009-10-07 09:33:05

    0

    我认为这样做最简单的方法是包装线程执行的代码在一个循环

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

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

    +0

    但是,如果我实现一个基类,嵌入线程逻辑,比我需要等待线程退出最派生类析构函数?如果我不会在基类析构函数中执行此操作,那么派生类析构函数将事先执行,并且线程函数(此运行时)将有机会使用已销毁的数据成员。 - idimba 0秒前 – dimba 2009-10-11 07:23:53

    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 
    
    +0

    完整的设计文档可以从http://code.google.com/p/effoaddon/downloads/list下载,名称为EffoAddons.pdf,“多线程”部分。 – Test 2009-10-13 05:20:16