Linux单列模式实现线程池

news2025/1/19 11:17:59

目录

一、单列模式

1.1 单列模式概念以及实现条件

1.2 饿汉模式

1.1.1 饿汉模式代码实现

 1.1.2 饿汉模式特征和优缺点

1.3 懒汉模式

 1.3.1 懒汉模式代码实现

 1.3.2 懒汉模式特征以及优缺点

二、线程池

2.1 线程池概念

2.2 实现简单线程池逻辑

2.3 模拟实现懒汉模式线程池

2.3.1 mutex.hpp 封装锁

2.3.2 Task.hpp 任务封装

💡💡2.3.3 Thread.hpp 线程封装

2.3.4 ThreadPool.hpp 线程池封装

2.3.5 main.cc 上层代码

2.3.6 执行结果展示

2.4 线程池应用场景


一、单列模式

1.1 单列模式概念以及实现条件

单例模式主要确保一个类只有一个实例!也就是对象唯一。其中饿汉、懒汉模式是单列模式的一种。

使对象唯一的条件:

  1. 私有的构造函数:单例类应当拥有一个私有的构造函数,以防止外部代码创建该类的实例。
  2. 公有的静态方法:单例类应当提供一个公有的静态方法,以供外部代码获取该类的唯一实例。
  3. 私有的静态变量:单例类应当拥有一个私有的静态变量,用于存储该类的唯一实例。
  4. 线程安全:在多线程环境下,应当保证单例类的唯一实例的创建和获取都是线程安全的。

1.2 饿汉模式

1.1.1 饿汉模式代码实现

class Singleton {  
public:  
    //静态成员函数--》不需要this指针
    static Singleton& getInstance() {  
        static Singleton instance; //静态成员对象--》生命周期长
        return instance;  
    }  
  
    // 防止拷贝构造函数和赋值运算符  
    Singleton(const Singleton&) = delete;  
    Singleton& operator=(const Singleton&) = delete;  
  
private:  
    Singleton() {}  //构造函数私有化
};

 1.1.2 饿汉模式特征和优缺点

特征:

  1. 类加载时就创建唯一实例,保证了一个类只有一个实例。
  2. 实现起来比较简单,没有多线程同步问题

优点:

  1. 由于实例在类加载时就被创建,因此不会造成资源的浪费。
  2. 没有多线程同步问题,保证了线程安全

缺点:

  1. 如果该类从未被使用,那么它的实例对象就会被创建并一直占用内存,即使该实例对象可能永远不会被使用。

1.3 懒汉模式

 1.3.1 懒汉模式代码实现

#include <mutex>  
  
//懒汉模式 main函数之后创建对象
class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		//双重检查 最外面if可以保证不会每次都加锁判断
		if (_psins == nullptr)
		{
			LockGuard<mutex> lock(_smtx);
			if (_psins == nullptr)
			{
				_psins = new InfoSingleton;
			}
			
		}
		return *_psins;
	}

private:
	InfoSingleton() {}
	//封死拷贝赋值构造
	InfoSingleton(const InfoSingleton& s) = delete;
	InfoSingleton& operator=(const InfoSingleton& s) = delete;
	static InfoSingleton* _psins;
	static mutex _smtx;
};
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;

 1.3.2 懒汉模式特征以及优缺点

特征:

        懒汉模式是一种单例模式,它的特点是在第一次使用实例对象时,才创建对象

优点:

  1. 在第一次使用实例对象时,创建对象进程启动无负载。也就是说,如果单例对象构造十分耗时或者占用很多资源,如加载插件、初始化网络连接、读取文件等等,而有可能该对象在程序运行时并不会被使用,那么在程序一开始就进行初始化会导致程序启动时非常缓慢,使用懒汉模式(延迟加载)则能避免这种情况。

缺点:

  1. 懒汉模式相对于饿汉模式来说,实现上更复杂一些,因为涉及到线程安全问题。

二、线程池

2.1 线程池概念

线程池是一种多线程处理形式,它预先将任务添加到队列中,然后在创建线程后自动启动这些任务。线程池中的线程都是后台线程,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。


2.2 实现简单线程池逻辑

线程池的工作原理如下:

  1. 线程池的初始化:在创建线程池后,线程池会根据配置初始化一定数量的核心线程,等待任务来临。--》单列模式实现线程池,提高创建线程的效率,有任务来立刻有执行线程
  2. 线程的管理:线程池会管理线程的生命周期。当一个线程完成任务后,它会回到线程池中等待下一个任务--》利用一个数组来组织管理线程
  3. 任务的提交:线程池提供了接口供外部提交任务,这些任务会被封装为一个个的工作单元并加入到线程池的工作队列中。--》任务队列 Push接口放任务到队列,同步信号给线程
  4. 任务的执行:线程池中的线程会循环从工作队列中取出任务并执行。--》线程池Run->一个个线程Run互斥取任务
  5. 线程池的关闭:当不再需要线程池时,可以通过调用关闭方法来停止线程池。这将会停止所有正在执行的任务,销毁所有的核心线程,并释放线程池相关的资源。


2.3 模拟实现懒汉模式线程池

2.3.1 mutex.hpp 封装锁

RAII的思想,通过类生命周期来加锁解锁!

#include<iostream>
#include<pthread.h>


class Mutex
{
public:
    Mutex(pthread_mutex_t* lock_p = nullptr):lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_)
        pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_)
        pthread_mutex_unlock(lock_p_);
    }
private:
    pthread_mutex_t* lock_p_;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* mutex):mutex_(mutex)
    {
        mutex_.lock();
    }
    ~LockGuard()
    {
        mutex_.unlock();
    }
private:
    Mutex mutex_;
};

2.3.2 Task.hpp 任务封装

#pragma once
#include<functional>
#include<iostream>
#include<unistd.h>
#include<string>
// 任务对象
class Task
{
public:
    //==typedef
    using func_t = std::function<double(int, int, char)>; 
 
    Task() {}
 
    Task(func_t callback, int x = 0, int y = 0, char op = '+') : _x(x), _y(y), _op(op), _callback(callback)
    {
    }
    // 仿函数
    std::string operator()()
    {
        double ret = _callback(_x, _y, _op);
        char buffer[64];
        snprintf(buffer, sizeof buffer, "%d %c %d = %lf", _x, _op, _y, ret);
        return buffer;
    }
 
private:
    int _x;
    int _y;
    char _op;
    func_t _callback; // 回调函数
};
 
//处理数据函数
double calculator(int x, int y, char op)
{
    double ret = 0.0;
    switch (op)
    {
    case '+':
        ret = x + y;
        break;
    case '-':
        ret = x - y;
        break;
    case '*':
        ret = x * y;
        break;
    case '/':
        if (y == 0)
            ret = 0;
        else
            ret = (double)x / y;
        break;
    case '%':
        if (y == 0)
            ret = 0;
        else
            ret = x % y;
        break;
    default:
        break;
    }
    return ret;
}

 💡💡2.3.3 Thread.hpp 线程封装

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <assert.h>
using func_t = std::function<void *(void *)>;
namespace MyThread
{
    class Thread
    {
    public:
        Thread(func_t callback, void *args = nullptr) : _callback(callback), _args(args)
        {
            char namebuffer[64];
            snprintf(namebuffer, sizeof namebuffer, "thread %d ", ++threadNum);
            _name = namebuffer;
        }
        void start()
        {
            int n = pthread_create(&_tid, nullptr, start_route, (void *)this);
            assert(n == 0);
            (void)n;
        }

        void join()
        {
            pthread_join(_tid, nullptr);
        }

        std::string ThreadName()
        {
            return _name;
        }

        void *callback()
        {
            return _callback(_args);
        }

    private:
        //在类中调用线程执行流函数必须要static-->因为默认的函数是只有一个void*参数,如果没有static,会默认带有隐藏参数this!
        //仅仅使用stati后,还要考虑如何访问类成员变量\函数-->传入的void*参数是this!
        static void *start_route(void *args)
        {
            Thread *pt = static_cast<Thread *>(args);
            return pt->callback();
        }

    private: 
        pthread_t _tid; //线程ID
        void *_args;    //线程所拥有的资源
        std::string _name; //线程名称
        func_t _callback;  //回调函数
        static int threadNum;//线程对象个数
    };
    int Thread::threadNum = 0;
}


2.3.4 ThreadPool.hpp 线程池封装

#pragma once
#include "Thread.hpp"
#include "mutex.hpp"
#include "Task.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include <mutex>
using namespace MyThread;
static const int gnum = 5;

template <class T>
class ThreadPool
{
    //和Thread.hpp中的理由一样 static防this指针 args=this
    static void *Handeler(void *args)
    {
        while (true)
        {
            T t;
            ThreadPool<T> *Self = static_cast<ThreadPool<T> *>(args);
            {
                LockGuard lock(&Self->_mutex);
                while (Self->_TaskQueue.empty())
                {
                    pthread_cond_wait(&Self->_cond, &Self->_mutex);
                }
                t = Self->_TaskQueue.front();
                Self->_TaskQueue.pop();
            }
            std::cout << "线程[" << pthread_self() << "]处理完了一个任务: " << t() << std::endl;
        }
        return nullptr;
    }
    //构造函数私有 初始化创建一批线程
    ThreadPool(const int num = gnum) : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread(Handeler, this));
        }
    }

    //拷贝赋值封死
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T> &) = delete;

public:
    // 懒汉模式 延迟加载
    static ThreadPool<T> *GetInstance()
    {
        if (_tp == nullptr)
        {
            _singleton_lock.lock();
            if (_tp == nullptr)
                _tp = new ThreadPool<T>();
            _singleton_lock.unlock();
        }
        return _tp;
    }

    //执行线程
    void run()
    {
        for (const auto &t : _threads)
        {
            t->start();
            std::cout << t->ThreadName() << "starts..." << std::endl;
        }
    }
    
    //放数据接口
    void push(const T &in)
    {
        LockGuard lock(&_mutex);
        _TaskQueue.push(in);
        pthread_cond_signal(&_cond);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (const auto &t : _threads)
            delete t;
    }

private:
    std::vector<Thread *> _threads; //数组管理线程
    int _num;
    std::queue<T> _TaskQueue; //任务队列
    pthread_mutex_t _mutex;//互斥
    pthread_cond_t _cond;//  同步
    //单列模式
    static ThreadPool<T> *_tp;
    static std::mutex _singleton_lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_singleton_lock;

2.3.5 main.cc 上层代码

#include "ThreadPool.hpp"
#include <memory>
#include <unistd.h>

int main()
{
    srand((unsigned long)time(nullptr) ^ getpid());


    //初始化并运行线程
    ThreadPool<Task>::GetInstance()->run();

    sleep(2);

    while (true)
    {
        //1.获取任务
        const char *str = "+-*/%";
        int x = rand() % 10 + 1;
        int y = rand() % 5 + 1;
        char op = str[rand() % 5];
        Task t(calculator, x, y, op);
        //2.放入任务至队列中
        ThreadPool<Task>::GetInstance()->push(t);
        std::cout << "生产任务: " << x << " " << op << " " << y << " = ?" << std::endl;
        sleep(1);
    }

    while (true)
    {
        sleep(1);
    }

    return 0;
}

2.3.6 执行结果展示


2.4 线程池应用场景

线程池主要应用在以下场景中:

  1. 处理CPU密集型任务:线程池可以预先创建一定数量的线程,避免在线程需要时创建线程,从而提高效率。这对于处理CPU密集型任务非常有用,例如进行大量计算或者处理某个特定任务。
  2. 面向用户的功能聚合:当业务开发中涉及到大量的并发调用时,线程池可以通过封装调用为任务并行执行的方式,提高整体响应时间。例如,如果一个系统有很多用户请求,线程池可以管理这些请求的并发执行,避免请求的阻塞。

此外,线程池在以下情况中也可以发挥出其优势:

  1. 任务执行时间短且数量大:如果需要执行大量的小任务,每个任务的处理时间都很短,此时使用线程池可以更有效地利用资源。
  2. 任务的执行顺序无关紧要:如果任务之间的执行顺序并不重要,那么线程池可以让任务异步执行,提高整体效率。
  3. 需要执行重复任务:如果需要执行重复的任务,使用线程池可以避免重复创建和销毁线程,提高性能和效率。
  4. 任务的间歇性执行:如果任务是间歇性地执行,使用线程池可以在需要时立即提供服务,避免空闲时间过长。
  5. 任务的响应时间要求高:如果要求任务的响应时间非常短,使用线程池可以更快地处理任务,提高用户体验。
  6. 需要限制并发数:如果需要限制并发执行的线程数量,线程池可以通过控制最大并发数来管理资源的使用。

总的来说,线程池主要应用在需要对并发执行的任务进行高效、有序、可控制管理的场景中。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/997434.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【八大经典排序算法】:直接插入排序、希尔排序实现 ---> 性能大比拼!!!

【八大经典排序算法】&#xff1a;直接插入排序、希尔排序实现 ---> 性能大比拼&#xff01;&#xff01;&#xff01; 一、 直接插入排序1.1 插入排序原理1.2 代码实现1.3 直接插入排序特点总结 二、希尔排序 ( 缩小增量排序 )2.1 希尔排序原理2.2 代码实现2.3 希尔排序特点…

UE5、CesiumForUnreal实现瓦片坐标信息图层效果

文章目录 1.实现目标2.实现过程2.1 原理简介2.2 cesium-native改造2.3 CesiumForUnreal改造2.4 运行测试3.参考资料1.实现目标 参考CesiumJs的TileCoordinatesImageryProvider,在CesiumForUnreal中也实现瓦片坐标信息图层的效果,便于后面在调试地形和影像瓦片的加载调度等过…

【C++入门到精通】C++入门 ——搜索二叉树(二叉树进阶)

阅读导航 前言一、搜索二叉树简介1. 概念2. 基本操作⭕搜索操作&#x1f36a;搜索操作基本代码&#xff08;非递归&#xff09; ⭕插入操作&#x1f36a;插入操作基本代码&#xff08;非递归&#xff09; ⭕删除操作&#x1f36a;删除操作基本代码&#xff08;非递归&#xff0…

给老婆写的,每日自动推送暖心消息

文章の目录 一、起因二、环境准备三、创建nestjs项目四、控制器五、service服务层1、获取Access token2、组装模板消息数据3、获取下次发工资还有多少天4、获取距离下次结婚纪念日还有多少天5、获取距离下次生日还有多少天6、获取时间日期7、获取是第几个结婚纪念日8、获取相恋…

前端面试题JS篇(4)

浏览器缓存 浏览器缓存分为强缓存和协商缓存&#xff0c;当客户端请求某个资源时&#xff0c;获取缓存的流程如下&#xff1a; 先根据这个资源的一些 http header 判断它是否命中强缓存&#xff0c;如果命中&#xff0c;则直接从本地获取缓存资源&#xff0c;不会发请求到服务…

vivado xpm 使用和封装

vivado xpm 使用和封装 tools -> language templates

【JavaScript】WebAPI入门到实战

文章目录 一、WebAPI背景知识1. 什么是WebAPI&#xff1f;2. 什么是API&#xff1f; 二、DOM基本概念三、获取元素三、事件初识1. 点击事件2. 键盘事件 四、操作元素1. 获取/修改元素内容2. 获取/修改元素属性3. 获取/修改表单元素属性4. 获取/修改样式属性 五、操作节点1. 新增…

scratch还原轨迹 2023年5月中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析

目录 scratch还原轨迹 一、题目要求 1、准备工作 2、功能实现 二、案例分析

Python:安装Flask web框架hello world

安装easy_install pip install distribute 安装pip easy_install pip 安装 virtualenv pip install virtualenv 激活Flask pip install Flask 创建web页面demo.py from flask import Flask app Flask(__name__)app.route(/) def hello_world():return Hello World! 2023if _…

基于springboot实现的rabbitmq消息确认

概述 RabbitMQ的消息确认有两种。 一种是消息发送确认。这种是用来确认生产者将消息发送给交换器&#xff0c;交换器传递给队列的过程中&#xff0c;消息是否成功投递。发送确认分为两步&#xff0c;一是确认是否到达交换器&#xff0c;二是确认是否到达队列。 第二种是消费接…

【APUE】标准I/O库

目录 1、简介 2、FILE对象 3、打开和关闭文件 3.1 fopen 3.2 fclose 4、输入输出流 4.1 fgetc 4.2 fputc 4.3 fgets 4.4 fputs 4.5 fread 4.6 fwrite 4.7 printf 族函数 4.8 scanf 族函数 5、文件指针操作 5.1 fseek 5.2 ftell 5.3 rewind 6、缓冲相关 6.…

安装samba服务器

1.实验目的 &#xff08;1&#xff09;了解SMB和NETBIOS的基本原理 &#xff08;2&#xff09;掌握Windows和Linux之间&#xff0c;Linux系统之间文件共享的基本方法。 2.实验内容 &#xff08;1&#xff09;安装samba服务器。 &#xff08;2&#xff09;配置samba服务器的…

Visual Studio 线性表的链式存储节点输出引发异常:读取访问权限冲突

问题&#xff1a; 写了一个线性表的链式存储想要输出&#xff0c;能够输出&#xff0c;但是会报错&#xff1a;读取访问权限冲突 分析&#xff1a; 当我们输出到最后倒数第二个节点时&#xff0c;p指向倒数第二个节点并输出&#xff1b; 下一轮循环&#xff1a;p指向倒数第二…

Helm Kubernetes Offline Deploy Rancher v2.7.5 Demo (helm 离线部署 rancher 实践)

文章目录 1. 简介2. 预备条件3. 选择 SSL 配置4. 离线安装的 Helm Chart 选项5. 下载介质6. 生成证书7. 镜像入库8. 安装 rancher9. 配置 nodeport10. 配置 ingress11. 界面访问11.1 首页预览11.2 查看集群信息11.3 查看项目空间11.4 查看节点信息 1. 简介 Rancher 是一个开源…

17-数据结构-查找-(顺序、折半、分块)

简介&#xff1a;查找&#xff0c;顾名思义&#xff0c;是我们处理数据时常用的操作之一。大概就是我们从表格中去搜索我们想要的东西&#xff0c;这个表格&#xff0c;就是所谓的查找表&#xff08;存储数据的表&#xff09;。而我们怎么设计查找&#xff0c;才可以让计算机更…

lv4 嵌入式开发-4 标准IO的读写(二进制方式)

目录 1 标准I/O – 按对象读写 2 标准I/O – 小结 3 标准I/O – 思考和练习 文本文件和二进制的区别&#xff1a; 存储的格式不同&#xff1a;文本文件只能存储文本。除了文本都是二进制文件。 补充计算机内码概念&#xff1a;文本符号在计算机内部的编码&#xff08;计算…

2023/09/10

文章目录 1. 使用Vue单页面全局变量注意事项2. 伪元素和伪类3. Vue3中定义数组通常使用ref4. Vue Router的 $router 和 $route5. Vue路由中的query和params的区别6. vue3defineExpose({})属性不能重命名&#xff0c;方法可以重命名7. 显卡共享内存的原理8. deltaY9. 快速生成方…

电池2RC模型 + 开路电压法 + 安时积分 + 电池精度测试 + HPPC脉冲

电池2RC模型 电池2RC模型是一种等效电路模型&#xff0c;用于描述电池的动态特性。该模型将电池视为一个理想电容器和一个理想电阻的并联&#xff0c;其中理想电容器代表电池的化学反应&#xff0c;理想电阻代表电池的内阻。该模型适用于描述电池的充电和放电过程。 开路电压…

Java中如何判断字符串输入[hello,world]是不是字符串数组参数

Java中如何判断字符串输入[hello,world]是不是字符串数组参数&#xff1f; 在Java中&#xff0c;可以使用正则表达式来判断一个字符串是否符合字符串数组的参数格式。你可以使用matches()方法和对应的正则表达式进行判断。 以下是一个示例代码&#xff1a; public static bo…

SpringCloudGateway网关实战(二)

SpringCloudGateway网关实战&#xff08;二&#xff09; 本文我们在前文的基础上&#xff0c;开始讲gateway过滤器的部分内容。gateway的过滤器分为内置过滤器Filter和全局过滤器GlobalFilter。本章节先讲内置过滤器Filter。 需要先声明一下内置过滤器和全局过滤器之间的区别…