管理派生类中的线程生命周期
Managing thread life-cycle in derived class
我有一个基类,它充当多个同步事件处理策略的接口。现在,我希望这些策略能够异步处理事件。为了尽量减少代码重构,每个策略都有自己的内部线程用于异步事件处理。我主要关心的是如何管理这个线程的生命周期。派生的策略类是在代码库周围构造和销毁的,因此很难在策略类之外管理线程生命周期(启动/停止)。
最后我写了下面的代码:#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类析构函数中连接。Stackoverflow上的许多评论认为,在析构函数中加入线程是一个坏主意。然而,考虑到线程生命周期必须通过派生类的构造/销毁来管理的约束,我找不到一个更好的主意。有人有更好的建议吗? 在类被销毁时释放类创建的资源是一个好主意,即使其中一个资源是线程。然而,当在析构函数中执行任何重要任务时,通常值得花时间全面检查其含义。
析构函数
一般规则是不要在析构函数中抛出异常。如果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。线程中断点进入循环逻辑。作业关闭逻辑是根据中断点实现的。因此,在迭代期间到达中断点是至关重要的。否则,如果用户代码从未到达中断点,则可能导致死锁。
检查是线程的生命周期必须与对象的生命周期相关联,还是异步事件处理需要与对象的生命周期相关联。如果是后者,那么可能值得考虑使用线程池。线程池可以对线程资源提供更细粒度的控制,例如施加最大限制,以及最小化浪费的线程数量,例如不做任何事情的线程或花费在创建/销毁寿命较短的线程上的时间。
例如,考虑一个用户创建一个包含500个Dervied
类的数组的情况。是否需要500个线程来处理500个策略?或者25个线程可以处理500个策略?请记住,在某些系统上,线程的创建/销毁可能非常昂贵,甚至可能存在操作系统施加的最大线程限制。
总之,检查权衡,并确定哪些行为是可接受的。尽量减少代码重构是很困难的,特别是在更改线程模型时,这对代码库的各个领域都有影响。完美的解决方案很少可以获得,所以要确定涵盖大多数情况的解决方案。一旦明确定义了支持的行为,就可以修改现有的代码,使其符合支持的行为。
- 从不同线程使用int64的不同字节安全吗
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C++中使用cURL和多线程
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在cuda线程之间共享大量常量数据
- 如何将元素添加到数组的线程安全函数?
- 线程,如果else语句,都是错误的上下文切换后,会发生什么
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 异常属于C++中的线程还是进程
- C++中的线程安全删除
- C++使用params创建线程函数会导致转换错误
- 类与私有变量的其他类之间的线程安全性
- CoInitialize()在单独的线程上崩溃而不返回
- c++中的线程池
- 线程之间的布尔停止信号
- 为什么std::async使用同一个线程运行函数
- 用于矢量处理的多个线程
- 松弛的内存顺序效果是否可以扩展到执行线程的生命周期之后?
- 管理派生类中的线程生命周期