我的POCO C++库学习 -- 线程 (四)
我的POCO C++库学习 -- 线程 (四)5. 主动对象5.1 线程回顾
在讨论主动对象之前,我想先说一下对于Poco中多线程编程的理解。大家都知道,对于多线程编程而言最基本的元素只有两个数据:锁和线程。线程提高了程序的效率,也带来了数据的竞争,因此为了保证数据的正确性,孪生兄弟"锁"随之产生。
对于不同的操作系统和编程语言而言,线程和锁通常是以系统API的方式提供的,不同语言和不同操作系统下API并不相同,但线程和锁的特性是一致的,这也是对线程和锁进行封装的基础。比如所有的系统线程API都提供了线程开始函数,其中可以设置线程的入口函数,提供了线程终止等功能。用面对对象的思想对线程和锁进行封装后,线程和锁就可以被看成编程时的一个基本粒子,一堆积木中的一个固定模块,用来搭建更大的组件。
除了线程和锁这两个基本模块之外,定时器和线程池也比较常用。线程池多用作线程频繁创建的时候。在Poco中,把线程池封装成为一个对象,池中的线程在池存在时始终存活,只不过是线程状态有所不同,不是运行中就是挂起。如果把线程看成一种资源的话,线程资源的申请和释放被放入了线程池的构造和析构函数中,Poco的这种封装也就是C++推荐的方法。
在Poco的线程池实现中,ThreadPool类还提供了一个线程池的单件接口。这个由静态函数定义:
从类图中我们可以看到Activity的线程驱动来自于默认的线程库。在Activity中为了调用到用户的真实业务函数,需要把对象实例和类的函数入口传进Activity中。这在Activity的构造函数中实现。
5.5 ActiveDispatcher ActiveDispatcher的特性如下:
1. ActiveMethod的默认行为并不符合经典的主动对象概念,经典的主动对象定义要求主动对象支持多个方法,并且各方法能够在单线程中被顺序执行。
2. 为了实现经典主动对象的行为,ActiveDispatcher被设计成主动对象的基类。
3. 通过使用ActiveDispatcher可以顺序执行用户业务函数。
让我们来看一下ActiveDispatcher的类图:
在类图中用户业务被打包成为MethodNotification对象放入了ActiveDispatcher的queue容器中,然后被顺序执行。其中Notification和NotificationQueue我们在以后介绍。
下面是其一个实现的例子:#include "Poco/ActiveMethod.h"#include "Poco/ActiveResult.h"#include "Poco/ActiveDispatcher.h"#include <utility>using Poco::ActiveMethod;using Poco::ActiveResult;class ActiveAdder: public Poco::ActiveDispatcher{public: ActiveObject(): add(this, &ActiveAdder::addImpl) {} ActiveMethod<int, std::pair<int, int>, ActiveAdder, Poco::ActiveStarter<Poco::ActiveDispatcher> > add;private: int addImpl(const std::pair<int, int>& args) { return args.first + args.second; }};int main(int argc, char** argv){ ActiveAdder adder; ActiveResult<int> sum = adder.add(std::make_pair(1, 2)); sum.wait(); std::cout << sum.data() << std::endl; return 0;}
5.6 其他在讨论完Poco主动对象的实现侯,回过来我们再讨论一下为什么要使用主动对象,使用Poco中主动对象的好处。
使用主动对象的好处很明显,能够使业务的划分更加清晰。但是我并不推荐在真实的项目中使用Poco封装的主动对象,除非是一些非常简单的场景。原因如下:
1. 在真实的业务应用中,我们能够很容易的在Thread和runnable基础上自己封装一个主动对象。
2. 真实业务不需要抽象,业务都是具体的。不使用主动对象的可读性会更加好。
虽然我不推荐在真实项目中使用Poco的主动对象,但是我觉得学习Poco中主动对象的实现仍然很有意义,特别是泛型和其他一些编程技巧。
(版权所有,转载时请注明作者和出处)