我用过的设计模式|我用过的设计模式(3)-- 观察者模式

我用过的设计模式|我用过的设计模式(3)-- 观察者模式
文章图片


文章目录

    • 前言
    • 观察者模式
    • 观察者模式案例(线程池)
    • 观察者模式的优势
    • 注意事项

前言 【我用过的设计模式|我用过的设计模式(3)-- 观察者模式】关于设计模式,这次我要一改常态,我就挑重要的讲。那些碎碎的就算了。
观察者模式 说到观察者模式,那自然是离不开线程了。
什么是观察者模式呢?顾名思义,是一种触发机制。在电视里见过埋手雷不?某个倒霉蛋不小心扯到了手雷的线,轰的一声儿手雷炸了,倒霉蛋瞬间连渣都没得了。
这就是观察者模式,其中要素有:监视者、消息传递、响应者。
那根线就是监视者,消息传递方式为线拉动了手雷的保险栓,响应者为手雷,轰的一声就是它的响应。
观察者模式案例(线程池) 这段代码后面还看得到,因为享元模式的一个很经典的案例也是线程池。。。
//Pthread_pool.h#pragma once#include #include #include //据说list不安全,不安全就不安全吧,更不安全的都忍了 #include "Cond.h" //封装过的条件变量类,继承自封装的mutex锁类,所以具有锁和条件变量的双重属性using namespace std; class Task //任务接口,每个任务必须实现的接口,以供工作线程调度任务的执行 {public: Task() { } virtual ~Task() { } virtual int run() = 0; //留给子类实现 }; typedef list list_task; //任务队列,用于暂存等待处理的任务,等待线程唤醒时处理,提供一种缓冲机制。class Pthread_Pool //线程池类 {public: Pthread_Pool(unsigned int max = 100, unsigned int min = 10, unsigned int wait = 60); ~Pthread_Pool(); void addTask(Task* task); // 往任务队列中添加新线程private: static void* taskThread(void* arg); // 工作线程 void createThread(); // 新建一个线程 void destroyThread(); // 销毁一个线程池unsigned int maxcount; // 最大线程数 unsigned int mincount; // 最小线程数 unsigned int count; // 当前线程池中线程数 unsigned int waitcount; // 等待线程数 unsigned int waitsec; // 等待时间 list_tasktaskList; //任务队列 Cond taskCond; //任务锁,线程接任务时使用 Cond cond; //线程锁,创建线程时使用 bool Stop; //线程池是否被允许运作,初始化线程池对象时置0,线程池销毁时置为1 };

#include "Pthread_Pool.h"//开放接口1 Pthread_Pool::Pthread_Pool(unsigned int max, unsigned int min, unsigned int wait) {//配置基本参数 count = 0; //当前线程池为空 waitcount = 0; //没有等待线程 mincount = min; //核心线程数(出厂配置) maxcount = max; //最大线程数(能承受的最高配置) waitsec = wait; //线程保活时长(过了时长还没接到任务,那就裁掉) Stop = false; //允许运作//上锁,创建一定数量的线程作为初始线程池 cond.lock(); for (unsigned i = 0; i < mincount; i++) {createThread(); //跳转到这个函数的实现->->->->-> } cond.unlock(); }Pthread_Pool::~Pthread_Pool() {destroyThread(); //销毁线程池 }void Pthread_Pool::createThread() {pthread_t tid; int ret = pthread_create(&tid, NULL, taskThread, (void*)this); //以执行taskThread()为目的创建线程,跳转到taskThread()函数的实现 ->->->->->if (ret < 0) perror("pthread create error"); else count++; }// 工作线程 void* Pthread_Pool::taskThread(void* arg) {pthread_detach(pthread_self()); //设置线程自分离属性 Pthread_Pool* pool = (Pthread_Pool*)arg; while (1) {pool->cond.lock(); //如果没有工作线程在等待 if (pool->taskList.empty()) {if (pool->Stop) //当收到线程池停止运行的消息时 {pool->count--; //线程数减一 pool->cond.unlock(); pthread_exit(NULL); //本线程强制退出 }pool->waitcount++; //等待任务的线程数加一 bool bSignal = pool->cond.timewait(pool->waitsec); //新任务等待被唤醒 pool->waitcount--; //没等到,没事干,喝西北风了// 删除无用线程 if (!bSignal && pool->count > pool->mincount) //如果没事干 && 有多余线程 {pool->count--; //先裁员一个,不要一次做绝了,反正是在while循环里面,没事干裁员机会多得是 pool->cond.unlock(); pthread_exit(NULL); } } pool->cond.unlock(); //记得要释放锁//如果有工作线程在等待 if (!pool->taskList.empty()) {pool->taskCond.lock(); //上任务锁 Task* t = pool->taskList.front(); //获取任务队列中最前端的任务并执行 pool->taskList.pop_front(); //移除被领取的任务 pool->taskCond.unlock(); //记得解锁t->run(); //任务开始 delete t; //弄完就删了 } } pthread_exit(NULL); }//开放接口2,向任务队列中添加任务 void Pthread_Pool::addTask(Task* task) {if (Stop) //线程池是否停止工作 return; //向任务队列中添加新任务 taskCond.lock(); //上任务锁 taskList.push_back(task); //添加任务 taskCond.unlock(); //记得解锁cond.lock(); //上线程锁 if (waitcount) //如果有空闲线程 {cond.signal(); //唤醒一个线程 } else if (count < maxcount) //如果没有空闲线程,一般来说,走到这里面来,那这个线程池的设计是有点失败了 {createThread(); //那就创建一个 cond.signal(); //然后唤醒 } cond.unlock(); }void Pthread_Pool::destroyThread() {printf("destroy?\n"); #if 0//强行清理 list_task::iterator it = taskList.begin(); for (; it!= taskList.end(); it++) {Task* t = *it; delete t; t = NULL; } taskList.clear(); #endif// 等待所有线程执行完毕 Stop = true; while (count > 0) {cond.lock(); cond.broadcast(); //广播 cond.unlock(); sleep(1); } }

这里面还配置了保证线程同步的锁,而观察者模式的唤醒,即采用条件变量来唤醒,一旦有任务的到来,会判断是否有空余线程,如果有,就直接唤醒一个去处理,如果没有,就会加入到任务队列中去。
观察者模式的优势
  • 观察者和被观察者之间是抽象耦合的,如此设计,不论是观察者还是被观察者,都可以独立拓展。
  • 建立了一套触发机制。
注意事项
  • 广播链问题
    如果一个对象,它既是观察者,又是被观察者,那就比较复杂了,我是还没遇到那种特别变态的广播链了,简单点的单行广播链还是可以应付的(每条链都是三个对象,用”中介+观察“就可以解决)。

    推荐阅读