当前位置: 首页 > news >正文

Linux多线程篇【5】——线程池

目录

  • 线程池
    • thread_pool.hpp
    • 任务
    • main.cc
  • 单例模式
    • 什么是设计模式
    • 饿汉方式和懒汉方式
      • 懒汉方式
    • 单例模式线程池

线程池

什么是线程池?在学STL接口的时候我们发现很多的结构都是有自动的扩容机制的,但频繁的扩容会有一定的代价,就好比我每次需要一块钱的时候每次都只向爸爸要一块钱,而爸爸在距离我500m远的距离,那么当我总共需要10块钱的时候就要来回跑10趟,十分耗时间,那么我不如直接一次性跟他要10块钱,就算有多的我也就先放在身上,有需要的时候就可以直接使用。内存池就是一次性去跟系统申请一大块内存空间来减少申请内存的次数。

与内存池类似,创建线程也是需要一定的代价的,因此我们也可以采用一次性申请多个线程的方式来提高效率。我们接下来的代码希望能够实现如下的功能

  1. 创建多个线程
  2. 将任务放入任务队列
  3. 线程以竞争的方式来获取任务队列中的任务,并执行任务

thread_pool.hpp

namespace ssj_thread_pool
{
    const int g_num = 5;
    template<class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;

        pthread_mutex_t _mtx;
        pthread_cond_t _cond;
    public:
    // 将lock,unlock,wait,wakeup,isempty进行封装不但使用
    // 方便而且因为Routine是静态的,所以无法访问类内私有成员(mtx
    // ,cond,taskqueue)将他们封装可以解决这个问题。
        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&_cond, &_mtx);
        }
        void WakeUp()
        {
            pthread_cond_signal(&_cond);
        }
        bool IsEmpty()
        {
            return _task_queue.empty();
        }
        // 在类内的成员函数会被默认传递this指针,因此需要使用静态成员函数
        static void* Routine(void *args)
        {
        	// 线程分离,无需再进行等待
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T>*)args;

            while (true)
            {
                tp->Lock();
                // 使用循环判断防止伪唤醒
                while (tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->Unlock();
				// 将执行任务放在锁外进行,这样可以让其
				// 它线程在任务执行时继续从任务队列读取
				// 任务达到多任务并发执行的效果
                t();
            }
        }
    public:
        void InitThreadPool()
        {
            for (int i = 0; i < _num; i++)
            {
                pthread_t tid;
                /* 将对象地址传给Routine,因为Routine是
                静态成员函数,没有this指针,所以不能访问类
                内成员,因此将this指针传给它*/
                pthread_create(&tid, nullptr, Routine, (void*)this);
                sleep(1);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();
            WakeUp();
        }
        void PopTask(T *out)
        {
            *out = _task_queue.front();
            _task_queue.pop();
        }
        ThreadPool(int num = g_num)
        : _num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        }
    };
}

任务

我们创建的任务就是用x和y来执行op对应的±*/%运算

namespace ssj_task
{
    class Task
    {
    private:
        int _x;
        int _y;
        char _op;
    public:
        Task() {}
        Task(int x, int y, char op)
        : _x(x)
        , _y(y)
        , _op(op)
        {}
        std::string Show()
        {
            std::string message = std::to_string(_x);
            message += _op;
            message += std::to_string(_y);
            message += "=?";
            return message;
        }
        int Run()
        {
            int res = 0;
             switch(_op)
             {
                case '+':
                    res = _x + _y;
                    break;
                case '-':
                    res = _x - _y;
                    break;
                case '*':
                    res = _x * _y;
                    break;
                case '/':
                    res = _x / _y;
                    break;
                case '%':
                    res = _x % _y;
                    break;
                default:
                    std::cout << "Please choose another option" << std::endl;
                    break;
             }
             std::cout << "当前任务正在被:" << pthread_self() << "处理" \
             << _x << _op << _y << "=" << res << std::endl;
             return res;
        }
        int operator()()
        {
            return Run();
        }
        ~Task() {}
    };
}

main.cc

int main()
{
    ThreadPool<Task> *tp = new ThreadPool<Task>();
    tp->InitThreadPool();
    srand((long long)time(nullptr));
    while(true)
    {
        Task t(rand() % 20 + 1, rand() % 10 + 1, "+-*/%"[rand() % 5]);
        tp->PushTask(t);
    }
    return 0;
}

单例模式

单例模式是一种经典的设计模式。对于某些类只应该具有一个对象,就称之为单例。我们定义对象时实际上需要两个步骤:

  • 1.开辟空间
  • 2.给空间写入初始值
    这两个步骤本质上就是把对象加载入内存,而单例模式就是只让该对象在内存中存在一份。一般而言需要被设计成单例模式的对象主要有如下的特征:
  • 语义上只需要一个对象。(只需要一个对象)
  • 该对象内部存在大量的空间,保存了大量的数据,如果允许该对象存在多份,或者允许发生各种拷贝,内存中会存在冗余数据。
    那么我们应该选择在什么时候将对象加载入内存呢?我们又对此区分了饿汉方式和懒汉方式。

什么是设计模式

设计模式可以看作是一种经验,根据一些经典的场景,给定了一些特定的解决方案,这些解决方案就是设计模式

饿汉方式和懒汉方式

就像不同的同学写暑假作业会选择在不同的时间一样,有的同学可能会 在领到暑假作业后前几天就把暑假作业完成了,这种方式就称为饿汉方式,而有的同学心理承受能力极强,对于堆积如山的作业嗤之以鼻,相信自己能够在开学之前的那个晚上把他们轻松完成,这种方式就称为懒汉方式。不难看出对于第一类同学来说,他们在假期开始的前几天可能会比较痛苦,但是后面就会玩的很开心,而第二类同学则恰好相反,在假期开始时他们可以玩的很爽,但是在最后他们也会经历补作业的痛苦。

懒汉方式

懒汉方式最核心的思想是“延时加载”,从而能够优化服务器的启动速度。写时拷贝就是懒汉方式的一个例子。
我们之前写的线程池其实就是一个单例,因为我们只需要一个线程池就够了,如果我们需要更多的线程,那么直接创建更多的线程就好了。因此接下来我们就试着用懒汉方式来修改一下我们线程池的代码。

单例模式线程池

  1. 将构造函数赋值语句等设为私有
  2. 在类外初始化对象指针
  3. 获取对象函数
namespace ssj_thread_pool
{
    const int g_num = 5;
    template <class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;

        pthread_mutex_t _mtx;
        pthread_cond_t _cond;
        static ThreadPool<T> *_ins;

    private:
        // 构造函数必须实现,且必须私有
        ThreadPool(int num = g_num)
            : _num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }

        ThreadPool(const ThreadPool<T> &tp) = delete;

        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;

    public:
        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_mtx);
        }
        void Wait()
        {
            pthread_cond_wait(&_cond, &_mtx);
        }
        void WakeUp()
        {
            pthread_cond_signal(&_cond);
        }
        bool IsEmpty()
        {
            return _task_queue.empty();
        }
        static void *Routine(void *args) // 在类内的成员函数会被默认传递this指针,因此需要使用静态成员函数
        {
            pthread_detach(pthread_self());
            ThreadPool<T> *tp = (ThreadPool<T> *)args;

            while (true)
            {
                tp->Lock();
                while (tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->Unlock();

                t();
            }
        }

    public:
        static ThreadPool<T> *GetInstance()
        {
            static pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;
            // 当前单例对象还没有被创建
            if (_ins == nullptr) // 申请锁的代价较高,因此使用双判断,减少锁的征用
            {
                pthread_mutex_lock(&_lock);
                if (_ins == nullptr)
                {
                    _ins = new ThreadPool<T>();
                    _ins->InitThreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
            }
            pthread_mutex_unlock(&_lock);

            return _ins;
        }

        void InitThreadPool()
        {
            for (int i = 0; i < _num; i++)
            {
                pthread_t tid;
                pthread_create(&tid, nullptr, Routine, (void *)this);
                sleep(1);
            }
        }
        void PushTask(const T &in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();
            WakeUp();
        }
        void PopTask(T *out)
        {
            *out = _task_queue.front();
            _task_queue.pop();
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        }
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::_ins = nullptr;
}

相关文章:

  • 指针成员操作符
  • python中应对各种机制
  • css实现时钟
  • “蔚来杯“2022牛客暑期多校训练营8 补题题解(F)
  • 【数据结构与算法】之深入解析“解出数学表达式的学生分数”的求解思路与算法示例
  • 给妈妈做个相册——在服务器上搭建Lychee相册的保姆级教程
  • 编程之路22
  • 适配器模式是个啥,在Spring中又用来干啥了?
  • 183. 从不订购的客户—not in()、左连接
  • LED灯实验
  • vue中ref的作用
  • JSP简介
  • 湖仓一体电商项目(八):业务实现之编写写入ODS层业务代码
  • 基于深度学习的多人步态识别系统(YOLOV5+DeepSort+GaitSet+Segmentation)
  • 计算机网络——组成、分类、性能指标、分层结构
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • 【Linux系统编程】快速查找errno错误码信息
  • 2019年如何成为全栈工程师?
  • Django 博客开发教程 16 - 统计文章阅读量
  • DOM的那些事
  • gulp 教程
  • java正则表式的使用
  • Netty源码解析1-Buffer
  • Odoo domain写法及运用
  • python_bomb----数据类型总结
  • python学习笔记 - ThreadLocal
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Spring Cloud中负载均衡器概览
  • Sublime Text 2/3 绑定Eclipse快捷键
  • swift基础之_对象 实例方法 对象方法。
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 删除表内多余的重复数据
  • 运行时添加log4j2的appender
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​MySQL主从复制一致性检测
  • ​低代码平台的核心价值与优势
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (原創) 物件導向與老子思想 (OO)
  • (状压dp)uva 10817 Headmaster's Headache
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .“空心村”成因分析及解决对策122344
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET委托:一个关于C#的睡前故事
  • .net专家(张羿专栏)
  • /etc/sudoers (root权限管理)
  • @Builder用法
  • @JSONField或@JsonProperty注解使用