在派生类中管理线程生命周期
Posted
技术标签:
【中文标题】在派生类中管理线程生命周期【英文标题】:Managing thread life-cycle in derived class 【发布时间】:2013-01-08 15:44:59 【问题描述】:我有一个 Base 类,它充当多个同步事件处理策略的接口。我现在想要异步处理事件的策略。为了最大限度地减少代码重构,每种策略都有自己的内部线程用于异步事件处理。我主要关心的是如何管理这个线程的生命周期。 Derived 策略类是在代码库周围构建和销毁的,因此很难在策略类之外管理线程生命周期(启动/停止)。
我最终得到了以下代码:
#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;
如您所见,线程被中断并加入到 Derived 类析构函数中。 *** 上的许多 cmets 认为在析构函数中加入线程是个坏主意。但是,考虑到必须通过 Derived 类的构造/销毁来管理线程生命周期的约束,我找不到更好的主意。有人有更好的提议吗?
【问题讨论】:
如何创建一个异步处理驱动策略的队列。那么只需在队列中添加和删除这些对象即可。 @mgr:事实上,我使用锁定队列将事件 push 到策略,而 processEvents 方法只是 pop(阻塞)事件这个队列。我从示例代码中删除了这些细节,因为它不会影响我主要关心的线程生命周期。 【参考方案1】:在类被销毁时释放类创建的资源是个好主意,即使其中一个资源是线程。但是,当在析构函数中执行任何重要任务时,通常值得花时间全面检查其含义。
析构函数
一般规则是not throw exceptions in destructors。如果Derived
对象位于从另一个异常展开的堆栈中,并且Derived::~Derived()
抛出异常,则将调用std::terminate()
,从而终止应用程序。虽然Derived::~Derived()
没有显式抛出异常,但重要的是要考虑到它正在调用的某些函数可能会抛出异常,例如_thread->join()
。
如果std::terminate()
是所需的行为,则无需更改。但是,如果不需要 std::terminate()
,则捕获 boost::thread_interrupted
并取消它。
try
_thread->join();
catch (const boost::thread_interrupted&)
/* suppressed */
继承
看起来继承被用于代码重用和通过将异步行为隔离到Base
层次结构内部来最小化代码重构。但是,一些样板逻辑也在Dervied
中。由于派生自 Base
的类已经必须更改,我建议考虑聚合或 CRTP 以尽量减少这些类中的样板逻辑和代码量。
例如可以引入一个辅助类型来封装线程逻辑:
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
的构造函数中聚合和初始化。它消除了对大部分样板代码的需求,并且可以在其他地方重用:
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 interruption point 进入循环逻辑。作业关闭逻辑是根据中断点来实现的。因此,在迭代期间达到中断点至关重要。否则,如果用户代码从未到达中断点,则可能会陷入死锁。
使用寿命
检查是线程的生命周期必须与对象的生命周期关联,还是异步事件处理需要与对象的生命周期关联。如果是后者,那么可能值得考虑使用线程池。线程池可以对线程资源提供更精细的控制,例如施加最大限制,并最大限度地减少浪费的线程数量,例如线程什么都不做或花费时间创建/销毁短期线程。
例如,考虑用户创建一个包含 500 个Dervied
类的数组的情况。处理 500 个策略需要 500 个线程吗?或者 25 个线程可以处理 500 个策略?请记住,在某些系统上,线程创建/销毁可能会很昂贵,甚至操作系统可能会施加最大线程限制。
最后,检查权衡,并确定哪些行为是可接受的。尽量减少代码重构可能很困难,特别是在更改对代码库的各个区域有影响的线程模型时。完美的解决方案很难获得,因此请确定涵盖大多数情况的解决方案。一旦明确定义了受支持的行为,就可以修改现有代码,使其处于受支持的行为范围内。
【讨论】:
以上是关于在派生类中管理线程生命周期的主要内容,如果未能解决你的问题,请参考以下文章