Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >我用过的设计模式(3)-- 观察者模式

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

原创
作者头像
看、未来
修改于 2021-02-25 02:15:28
修改于 2021-02-25 02:15:28
3440
举报
在这里插入图片描述
在这里插入图片描述

前言

关于设计模式,这次我要一改常态,我就挑重要的讲。那些碎碎的就算了。

观察者模式

说到观察者模式,那自然是离不开线程了。

什么是观察者模式呢?顾名思义,是一种触发机制。在电视里见过埋手雷不?某个倒霉蛋不小心扯到了手雷的线,轰的一声儿手雷炸了,倒霉蛋瞬间连渣都没得了。

这就是观察者模式,其中要素有:监视者、消息传递、响应者。

那根线就是监视者,消息传递方式为线拉动了手雷的保险栓,响应者为手雷,轰的一声就是它的响应。


观察者模式案例(线程池)

这段代码后面还看得到,因为享元模式的一个很经典的案例也是线程池。。。

代码语言:txt
AI代码解释
复制
//Pthread_pool.h

#pragma once

#include <pthread.h>
#include <unistd.h>
#include <list>	//据说list不安全,不安全就不安全吧,更不安全的都忍了
#include "Cond.h"	//封装过的条件变量类,继承自封装的mutex锁类,所以具有锁和条件变量的双重属性

using namespace std;

class Task	//任务接口,每个任务必须实现的接口,以供工作线程调度任务的执行
{
public:
    Task() {}
    virtual ~Task() {}
    virtual int run() = 0; //留给子类实现
};

typedef list<Task*> 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_task	 taskList;      //任务队列
    Cond taskCond;    //任务锁,线程接任务时使用
    Cond cond;        //线程锁,创建线程时使用
    bool Stop;                  //线程池是否被允许运作,初始化线程池对象时置0,线程池销毁时置为1
};
代码语言:txt
AI代码解释
复制
#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);
    }
}

这里面还配置了保证线程同步的锁,而观察者模式的唤醒,即采用条件变量来唤醒,一旦有任务的到来,会判断是否有空余线程,如果有,就直接唤醒一个去处理,如果没有,就会加入到任务队列中去。


观察者模式的优势

  • 观察者和被观察者之间是抽象耦合的,如此设计,不论是观察者还是被观察者,都可以独立拓展。
  • 建立了一套触发机制。

注意事项

  • 广播链问题 如果一个对象,它既是观察者,又是被观察者,那就比较复杂了,我是还没遇到那种特别变态的广播链了,简单点的单行广播链还是可以应付的(每条链都是三个对象,用”中介+观察“就可以解决)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】勉强能看的线程池详解
线程池这东西,用了几次还是不得其解,简直是:求之不得,寤寐思服。悠哉悠哉,辗转反侧。
看、未来
2020/07/11
7590
我用过的设计模式(7)--享元模式
之前写“桥接模式”的时候,说“桥接模式”是最抽象的设计模式,那是因为我没接触到“享元模式”。
看、未来
2021/03/01
2810
FTP文件管理项目(本地云)项目日报(五)
有点尴尬啊,可能是其他人都等着开完会才写日报,或者是写完还没过审,然后今天大家都太忙。。。
看、未来
2020/08/26
7930
FTP文件管理项目(本地云)项目日报(五)
我用过的设计模式(8)-- 装饰者模式
动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更加灵活。
看、未来
2021/03/01
3070
开发成长之路(14)-- 小项目:视频点播器服务端(放码过来)
开发成长之路(1)-- C语言从入门到开发(入门篇一) 开发成长之路(2)-- C语言从入门到开发(函数与定制输入输出控制函数) 开发成长之路(3)-- C语言从入门到开发(讲明白指针和引用,链表很难吗?) 开发成长之路(4)-- C语言从入门到开发(距离开发,还差这一篇) 开发成长之路(5)-- C语言从入门到开发(仿ATM机项目,我写的第一个项目) 开发成长之路(6)-- C++从入门到开发(C++入门不难) 开发成长之路(6)-- C++从入门到开发(C++知名库:STL入门·容器(一)) 开发成长之路(7)-- C++从入门到开发(C++知名库:STL入门·容器(二)) 开发成长之路(8)-- C++从入门到开发(C++知名库:STL入门·容器(三)) 开发成长之路(9)-- C++从入门到开发(C++知名库:STL入门·空间配置器) 开发成长之路(10)-- C++从入门到开发(C++知名库:STL入门·算法) 开发成长之路(11)-- STL常用函数大集合 开发成长之路(12)-- Linux网络服务端编程(通识篇之熟悉操作环境) 开发成长之路(13)-- Linux网络服务端编程(通识篇)
看、未来
2021/09/18
4920
手写线程池 - C语言版
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
C语言与CPP编程
2021/10/09
2K0
手写线程池 - C++版
在《手写线程池 - C语言版》中,已经实现了 C 语言版的线程池,如果我们也学过 C++ 的话,可以将其改为 C++ 版本,这样代码不管是从使用还是从感观上都会更简洁一些。
C语言与CPP编程
2021/10/09
1.4K0
【C】高并发线程池设计
高并发线程池设计 并发基本概念 所谓并发编程指的是在同一台计算机上"同时"处理多个任务。 并发是在同一实体上的多个事件。 ---- 处理事件过程出现阻塞 漫长的CPU密集型处理。 读取文件,但文件尚未缓存,从硬盘中读取较为缓慢。 不得不等待获取某个资源: 硬件驱动 互斥锁 等待同步方式调用的数据库响应 网络上的请求和响应 多线程的缺陷 单个进程或线程同时只能处理一个任务,如果有很多请求需要同时处理怎么办? 解决方案——运用多进程或多线程技术解决。 缺陷: 创建和销毁
半生瓜的blog
2023/05/13
7110
【C】高并发线程池设计
C语言实现线程池
实现线程池的基本思路是:先创建几个固定的线程,让每个线程运行起来,然后通过互斥锁和条件变量使得每个线程进入等待状态,当需要分派线程时,改变条件变量,使得某个线程退出等待状态开始执行传入的函数参数,执行完后重新进入等待状态。
叶茂林
2024/01/03
5980
Golang语言社区--【游戏服务器知识】多线程并发
引言:上篇文章说到了多进程并发式的服务端模型,如上一篇文章所述,进程的频繁创建会导致服务器不堪负载,那这一篇博客主要讲述的是线程模型和线程池的方式来提高服务端的负载能力。同时比较一下不同的模型的好处与坏处。 (如果不加以说明,我们都是考虑开发是基于GNU/Linux的)在Linux下创建一个线程的方式很简单,pthread_create() 函数来创建线程,其中的一个参数的回调函数,也就是线程本身的执行体函数。 void *thread_entry( void * args ); 这里不过多的强调怎样利用线
李海彬
2018/03/21
1K0
Linux C下线程池的使用
线程池也是多线程的处理方式。是将“生产者”线程提出任务添加到“任务队列”,然后一些线程自动完成“任务队列”上的任务。
Jasonangel
2021/05/28
1.8K0
【C++】开源:ThreadPoll线程池实现与使用
线程池是一种线程管理的抽象概念,它主要用于优化多线程应用程序的性能和资源利用。在多线程编程中,创建和销毁线程是一个开销较大的操作。线程池通过预先创建一组线程,并将任务提交给这些线程来执行,从而避免了重复创建和销毁线程的开销。
DevFrank
2024/07/24
5430
掌握并行处理:理解并构建自己的线程池
(1)线程使用场景:某类任务特别耗时,会严重影响该线程处理其他任务,因此需要在其他线程异步执行该任务。
Lion 莱恩呀
2024/09/23
1110
掌握并行处理:理解并构建自己的线程池
《C++并发编程实战》读书笔记(6):高级线程管理、并行算法函数、测试与除错
大多数程序中并不方便给每个任务分配单独的线程,但仍可通过线程池来充分利用可调配的并发算力:将可同时执行的任务提交到线程池,放入任务队列中等待,工作线程循环地领取并执行任务。
C语言与CPP编程
2023/09/19
3730
《C++并发编程实战》读书笔记(6):高级线程管理、并行算法函数、测试与除错
linux网络编程学习笔记之五 —–并发机制与线程�
简述下常见的进程和线程分配方式:(好吧,我仅仅是举几个样例作为笔记。。。并发的水太深了,不敢妄谈。。。)
全栈程序员站长
2022/07/12
3540
linux网络编程学习笔记之五 —–并发机制与线程�
线程(四)线程池的实现+线程的单例模式
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够 保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
海盗船长
2020/08/27
1.2K0
线程池的使用场景和代码实现!
大家周末好,今天给大家带来一篇技术文章,是关于线程池的实现和使用场景;我相信大家在公司里面的代码里面经常看到这个线程池的用法,或者甚至大家可能会听到内存池、对象池、连接池等这些专业术语,反正就很多带池的专业术语,不过你会发现他们都有一个共同的特点就是“屁股”末尾都带一个“池”字,池字,简单理解就是用来存东西的,举个简单例子来说,你比如游泳池里面可以用来存储水!
IOT物联网小镇
2021/09/09
3880
线程池的使用场景和代码实现!
Linux线程-生产消费模型和线程池
注:互斥关系保证的是数据的访问正常,而同步关系是为了让多线程(生产和消费者)之间协同起来
用户9645905
2022/11/30
3.3K0
Linux线程-生产消费模型和线程池
【Linux】线程池详解及其基本架构与单例模式实现
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
用户11316056
2024/11/19
1510
【Linux】线程池详解及其基本架构与单例模式实现
线程池--简单版本和复杂版本
    多线程版服务器一个客户端就需要创建一个线程! 若客户端太多, 显然不太 合适.     什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销 毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容 忽视,这时也是线程池该出场的机会了。如果线程创建和销毁时间相比任务执行 时间可以忽略不计,则没有必要使用线程池了。     实现的时候类似于生产者和消费
莫浅子
2023/11/03
2510
线程池--简单版本和复杂版本
推荐阅读
相关推荐
【C++】勉强能看的线程池详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档