POCO C++库学习和分析 -- 线程 (四)
5. 主动对象
5.1 线程回顾
在讨论主动对象之前,我想先说一下对于Poco中多线程编程的理解。大家都知道,对于多线程编程而言最基本的元素只有两个数据:锁和线程。线程提高了程序的效率,也带来了数据的竞争,因此为了保证数据的正确性,孪生兄弟"锁"随之产生。
对于不同的操作系统和编程语言而言,线程和锁通常是以系统API的方式提供的,不同语言和不同操作系统下API并不相同,但线程和锁的特性是一致的,这也是对线程和锁进行封装的基础。比如所有的系统线程API都提供了线程开始函数,其中可以设置线程的入口函数,提供了线程终止等功能。用面对对象的思想对线程和锁进行封装后,线程和锁就可以被看成编程时的一个基本粒子,一堆积木中的一个固定模块,用来搭建更大的组件。
除了线程和锁这两个基本模块之外,定时器和线程池也比较常用。线程池多用作线程频繁创建的时候。在Poco中,把线程池封装成为一个对象,池中的线程在池存在时始终存活,只不过是线程状态有所不同,不是运行中就是挂起。如果把线程看成一种资源的话,线程资源的申请和释放被放入了线程池的构造和析构函数中,Poco的这种封装也就是C++推荐的方法。
在Poco的线程池实现中,ThreadPool类还提供了一个线程池的单件接口。这个由静态函数定义:
static ThreadPool& defaultPool();
通过这个函数,使用者可以很方便的从Poco库中获取一个线程的起点,而无需关心线程维护的细节,这样使用者可以进一步把注意力放在需要实现的业务上。在实现了ThreadPool的这个接口后,Poco类中关于线程的更高级封装即可以实现。如定时器(Timer),主动对象(Activity Object),任务(Task)。
在Poco实现定时器,实现ThreadPool中的PooledThread,以及接下来要讨论的主动对象中的ActiveRunnable,RunnableAdapter,ActiveDispatcher时,可以发现这些类都从Runnable继承。这些类需要实现自己的run函数,只不过在run函数中做的工作不同。
定时器中的run函数工作就是计时,定期更新实现,至触发时刻运行使用者定义的用户事件。而PooledThread的工作则是,控制线程状态,在挂起和运行间切换,当有用户业务需要运行时,运行用户业务。说穿了这些类都是用户业务的一个代理。只不过代理时,实现的手段不同。
5.2 主动对象
总结了前几章后,让我们继续往下看一下主动对象。首先是什么是主动对象。Poco中对于主动对象有如下描述:主动对象是一个对象,这个对象使用自己线程运行自己的成员函数。
1. 在Poco中,主动对象支持两种主动成员函数。
2. Activity类型的主动对象使用在用户业务为不需要返回值和无参数的成员函数时侯。
3. ActiveMethod类型的主动对象使用在用户业务为需要返回值和需要参数的成员函数时侯。
4. 所有的主动对象即能够共享一个单线程,也可以拥有其自己的线程。
事实上在Poco库中实现了3种类型的主动对象,分别是Activity、ActiveMethod、ActiveDispatcher。其中ActiveDispatcher可以看成是ActiveMethod的一个变种。Activity和ActiveMethod的区别在于是否需要关心用户业务的返回值,因为ActiveMethod模板实现时也特化了一个没有用户参数的版本。
其实我们自己也能很容易的实现一个主动对象。简单的接口如下:
class MyObject : public Runnable
{
public:
void start();
void stop();
void run();
public:
void realrun();
public:
Thread _thread;
};
void MyObject::start()
{
_thread.start();
}
void MyObject::stop()
{
_thread.join();
}
void