首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >管理派生类中的线程生命周期

管理派生类中的线程生命周期
EN

Stack Overflow用户
提问于 2013-01-08 15:44:59
回答 1查看 1.7K关注 0票数 1

我有一个基类,它充当同步事件处理的多个策略的接口。现在我想要策略异步地处理这些事件。为了最小化代码重构,每个策略都有自己的异步事件处理内部线程。我主要关心的是如何管理这个线程的生命周期。派生策略类是围绕代码库构造和销毁的,因此很难管理策略类之外的线程生命周期(开始/停止)。

最后,我得到了以下代码:

代码语言:javascript
复制
#include <iostream>
#include <cassert>

#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>

struct Base
{
    virtual ~Base()
    {
        std::cout << "In ~Base()" << std::endl;

        // For testing purpose: spend some time in Base dtor
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
    }

    virtual void processEvents() = 0;

    void startThread()
    {
        if(_thread)
        {
            stopThread();
        }
        _thread.reset(new boost::thread(&Base::processEvents, this));
        assert(_thread);
    }

    void stopThread()
    {
        if(_thread)
        {
            std::cout << "Interrupting and joining thread" << std::endl;
            _thread->interrupt();
            _thread->join();
            _thread.reset();
        }
    }

    boost::shared_ptr<boost::thread> _thread;
};

struct Derived : public Base
{
    Derived()
    {
        startThread();
    }

    virtual ~Derived()
    {

        std::cout << "In ~Derived()" << std::endl;

        // For testing purpose: make sure the virtual method is called while in dtor
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

        stopThread();

    }

    virtual void processEvents()
    {
        try
        {
            // Process events in Derived specific way
            while(true)
            {
                // Emulated interruption point for testing purpose
                boost::this_thread::sleep(boost::posix_time::milliseconds(100));
                std::cout << "Processing events..." << std::endl;
            }
        }
        catch (boost::thread_interrupted& e)
        {
            std::cout << "Thread interrupted" << std::endl;
        }
    }
};

int main(int argc, char** argv)
{
    Base* b = new Derived;
    delete b;
    return 0;
}

如您所见,线程被中断,并在派生类析构函数中加入。许多关于Stackoverflow的评论认为,在析构函数中加入线程是个坏主意。但是,考虑到必须通过构造/销毁派生类来管理线程生命周期这一约束,我找不到更好的主意了。有人有更好的建议吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-01-13 22:05:08

在类被销毁时释放类创建的资源是一个好主意,即使其中一个资源是线程。然而,当在析构函数中执行任何非平凡的任务时,通常都值得花时间全面地检查其含义。

破坏者

一般的规则是不抛出析构函数中的异常。如果Derived对象位于从另一个异常展开的堆栈中,而Derived::~Derived()抛出一个异常,则将调用std::terminate(),从而杀死应用程序。虽然Derived::~Derived()没有显式抛出异常,但重要的是要考虑它正在调用的一些函数可能会抛出,例如_thread->join()

如果std::terminate()是所需的行为,则不需要进行任何更改。但是,如果不需要std::terminate(),则捕获boost::thread_interrupted并抑制它。

代码语言:javascript
复制
try
{
  _thread->join();
}
catch (const boost::thread_interrupted&)
{
  /* suppressed */ 
}

继承

看起来,通过隔离Base层次结构内部的异步行为,继承似乎被用于代码重用和最小化代码重构。然而,一些样板逻辑也在Dervied中。由于从Base派生的类已经需要更改,我建议考虑聚合或CRTP,以尽量减少这些类中样板逻辑和代码的数量。

例如,可以引入一个助手类型来封装线程逻辑:

代码语言:javascript
复制
class AsyncJob
{
public:
  typedef boost::function<void()> fn_type;

  // Start running a job asynchronously.
  template <typename Fn>
  AsyncJob(const Fn& fn)
    : thread_(&AsyncJob::run, fn_type(fn))
  {}

  // Stop the job.
  ~AsyncJob()
  {
    thread_.interrupt();

    // Join may throw, so catch and suppress.
    try { thread_.join(); }
    catch (const boost::thread_interrupted&) {}
  }

private: 

  //  into the run function so that the loop logic does not
  // need to be duplicated.
  static void run(fn_type fn)
  {
    // Continuously call the provided function until an interrupt occurs.
    try
    {
      while (true)
      {
        fn();

        // Force an interruption point into the loop, as the user provided
        // function may never call a Boost.Thread interruption point.
        boost::this_thread::interruption_point();
      }
    }
    catch (const boost::thread_interrupted&) {}
  }

  boost::thread thread_;
};

这个助手类可以在Derived的构造函数中聚合和初始化。它消除了许多样板代码的需要,可以在其他地方重用:

代码语言:javascript
复制
struct Derived : public Base
{
    Derived()
      : job_(boost::bind(&Base::processEvents, this))
    {}

    virtual void processEvents()
    {
      // Process events in Derived specific way
    }

private:

  AsyncJob job_;
};

另一个关键点是AsyncJob将一个Boost.Thread 中断点强制到循环逻辑中。作业关闭逻辑是根据中断点实现的。因此,在迭代期间到达中断点是至关重要的。否则,如果用户代码从未到达中断点,则可能会陷入死锁。

寿命

检查线程的生存期是否必须与对象的生存期相关联,或者是否需要将异步事件处理与对象的生存期相关联。如果是后者,则可能值得考虑使用线程池。线程池可以对线程资源提供更好的粒度控制,例如施加最大限制,并将浪费的线程数量降到最低,例如线程不做任何事情,或者创建/销毁短命线程所花费的时间。

例如,假设用户创建了一个由500个Dervied类组成的数组。需要500个线程来处理500个策略吗?或者25个线程可以处理500个策略?请记住,在某些系统上,线程创建/销毁可能是昂贵的,甚至可能存在操作系统施加的最大线程限制。

总之,检查权衡,并确定哪些行为是可以接受的。很难最小化代码重构,特别是当更改对代码库的各个领域有影响的线程模型时。完美的解决方案很少能得到,所以找出涵盖大多数情况的解决方案。一旦明确定义了受支持的行为,就可以修改现有代码,使其处于受支持的行为中。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14218720

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档