【Linux】线程池详解及其基本架构与单例模式实现

目录

1.关于线程池的基本理论         

1.1.线程池是什么?

1.2.线程池的应用场景:

2.线程池的基本架构

2.1.线程容器

2.2.任务队列

2.3.线程函数(HandlerTask)

2.4.线程唤醒机制

3.添加单例模式

3.1.单例模式是什么?

3.2.饿汉实现方式和懒汉实现方式

饿汉式单例模式:

懒汉式单例模式:

3.3.改写懒汉式的单例模式

双判断的方式为什么能减少单例的加锁成本呢?

单判断为什么会出错?

单例模式的注意点:

4.代码和执行效果

1.关于线程池的基本理论         

1.1.线程池是什么?

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

1.2.线程池的应用场景:

  1.  需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2.  对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

2.线程池的基本架构

  1. 线程容器:用来管理创建的线程,方便统一初始化。
  2. 任务队列:用来储存任务消息,需要支持压入与取出的操作。
  3. 线程函数(HandlerTask):线程都需要执行这个函数模块,在这个函数模块中进行任务的等待和执行。
  4. 线程唤醒机制:需要一个线程唤醒机制,通过条件变量和互斥锁完成对线程的保护与唤醒。
  5. 单例模式:线程池不需要创建多个,一个程序只需要一个线程池,通过单例模式进行优化。

2.1.线程容器

我们使用vector容器来存储线程,并且使用自己封装的线程来实现线程使用的各个接口

std::vector<Thread> _threads;

2.2.任务队列

我们使用队列这个容器来存储任务,并且利用队列FIFO的特性进行存储任务和取出任务

 std::queue<T> _task_queue;

2.3.线程函数(HandlerTask)

我们首先要明确线程需要死循环去执行任务,所以需要while一直循环,直到线程池已经退出了&&任务队列是空的。执行任务的同时还需要保证线程的安全,所以需要加锁来保证。

    void HandlerTask(std::string name)
    {
        LOG(INFO, "%s is running...", name.c_str());
        //线程需要死循环去处理任务
        while(true)
        {
            //1、保证队列安全
            LockQueue();
            //2、队列中不一定有数据
            while(_task_queue.empty() && _isrunning)
            {
                _waitnum++;
                ThreadSleep();
                _waitnum--;
            }
            //2.1 如果线程池已经退出了&&任务队列是空的
            if(_task_queue.empty() && !_isrunning)
            {
                UnlockQueue();
                break;
            }
            // 2.2 如果线程池不退出 && 任务队列不是空的
            // 2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后在退出
            // 3. 一定有任务, 处理任务
            T t = _task_queue.front();
            _task_queue.pop();
            UnlockQueue();
            LOG(DEBUG, "%s get a task", name.c_str());
            //4.处理任务,这个任务属于线程独占的任务
            //t();
            LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());
        }
    }

2.4.线程唤醒机制

需要一个线程唤醒机制,通过条件变量加互斥锁完成对线程的保护与唤醒。

3.添加单例模式

3.1.单例模式是什么?

某些类, 只应该具有一个对象(实例), 就称之为单例。在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

3.2.饿汉实现方式和懒汉实现方式

饿汉式单例模式:

饿汉式单例模式在类加载时就完成了实例的创建。这种方式的特点是线程安全,因为 JVM 在加载类时会对静态变量进行初始化,并且这个过程是线程互斥的。

template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};

缺点:程序启动的时候,可能会很慢!所以我们一般不用饿汉

懒汉式单例模式:

template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

缺点:存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.

其实在日常使用中,我们一般不会使用饿汉式单例模式,因为它启动的时候过慢,所以我们来改写基于懒汉式的单例模式,主要解决线程安全的问题!

3.3.改写懒汉式的单例模式

添加双判断来解决线程安全问题。

    static ThreadPool<T> *GetInstance()
    {
        // 如果是多线程获取线程池对象下面的代码就有问题了!!
        // 只有第一次会创建对象,后续都是获取
        // 双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全
        if (nullptr == _instance) // 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象
        {
            LockGuard lockguard(&_lock);
            if (nullptr == _instance)
            {
                _instance = new ThreadPool<T>();
                _instance->InitThreadPool();
                _instance->Start();
                LOG(DEBUG, "创建线程池单例");
                return _instance;
            }
        }
        LOG(DEBUG, "获取线程池单例");
        return _instance;
    }

双判断的方式为什么能减少单例的加锁成本呢?

我们主要解决的是害怕多线程创建不止一个单例,我们的目的是让该单例模式只生产一个单例!围绕这一个核心去解决问题!

同时有很多进程过来的时候,都会去尝试加锁,但是只有一个线程可以加锁成功,然后会执行new操作,这时候_instance == nullptr就不成立了,再后来的线程不会等待在锁上了,直接判断外层的if就会退出了,不然所有的线程都要等待锁了。

单判断为什么会出错?

同时可能多个线程通过if判断,等待锁,第一个线程加锁完成之后,执行创建,退出之后其他线程可以继续抢锁,抢到以后继续创建,就保证不了线程安全!

单例模式的注意点:

  • 单例模式下的构造函数必须要有,但必须是私有的。
  • 赋值和拷贝函数禁用,因为只创建1个单例
  • 在类里面创建的静态变量在类内定义,需要在类外初始化

4.代码和执行效果

代码:

#pragma once

#include <iostream>
#include <vector>
#include <queue>
#include <pthread.h>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"

using namespace ThreadModule;

const static int gdefaultthreadnum = 5;

template <typename T>
class ThreadPool
{
public:
    static ThreadPool<T> *GetInstance()
    {
        // 如果是多线程获取线程池对象下面的代码就有问题了!!
        // 只有第一次会创建对象,后续都是获取
        // 双判断的方式,可以有效减少获取单例的加锁成本,而且保证线程安全
        if (nullptr == _instance) // 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象
        {
            LockGuard lockguard(&_lock);
            if (nullptr == _instance)
            {
                _instance = new ThreadPool<T>();
                _instance->InitThreadPool();
                _instance->Start();
                LOG(DEBUG, "创建线程池单例");
                return _instance;
            }
        }
        LOG(DEBUG, "获取线程池单例");
        return _instance;
    }

    void Stop()
    {
        LockQueue();
        _isrunning = false;
        ThreadWakeup();
        UnlockQueue();
    }

    void Wait()
    {
        for(auto &thread : _threads)
        {
            thread.Join();
            LOG(INFO, "%s is quit...", thread.name().c_str());
        }
    }

    bool Enqueue(const T &t)
    {
        bool ret = false;
        LockQueue();
        if(_isrunning)
        {
            _task_queue.push(t);
            if(_waitnum > 0)
            {
                ThreadWakeup();
            }
            LOG(DEBUG, "enqueue task success");
            ret = true;
        }
        UnlockQueue();
        return ret;
    }

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

private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void ThreadSleep()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void ThreadWakeup()
    {
        // 唤醒一个等待特定条件变量的线程
        pthread_cond_signal(&_cond);
    }
    void ThreadWakeupAll()
    {
        // 唤醒所有等待特定条件变量的线程
        pthread_cond_broadcast(&_cond);
    }

    // 单例模式下的构造函数必须要有,但必须是私有的
    ThreadPool(int threadnum = gdefaultthreadnum) : _threadnum(threadnum), _waitnum(0), _isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        LOG(INFO, "ThreadPool Construct()");
    }

    // 赋值和拷贝函数禁用,因为只创建1个单例
    ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;
    ThreadPool(const ThreadPool<T> &) = delete;

    void Start()
    {
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }

    void InitThreadPool()
    {
        //构建出所有的线程,并不启动
        for(int num = 0; num < _threadnum; num++)
        {
            std::string name = "thread" + std::to_string(num+1);
            //bind函数到底有什么作用???
            _threads.emplace_back(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name);
            LOG(INFO, "init thread %s done", name.c_str());
        }
    }

    void HandlerTask(std::string name)
    {
        LOG(INFO, "%s is running...", name.c_str());
        //线程需要死循环去处理任务
        while(true)
        {
            //1、保证队列安全
            LockQueue();
            //2、队列中不一定有数据
            while(_task_queue.empty() && _isrunning)
            {
                _waitnum++;
                ThreadSleep();
                _waitnum--;
            }
            //2.1 如果线程池已经退出了&&任务队列是空的
            if(_task_queue.empty() && !_isrunning)
            {
                UnlockQueue();
                break;
            }
            // 2.2 如果线程池不退出 && 任务队列不是空的
            // 2.3 如果线程池已经退出 && 任务队列不是空的 --- 处理完所有的任务,然后在退出
            // 3. 一定有任务, 处理任务
            T t = _task_queue.front();
            _task_queue.pop();
            UnlockQueue();
            LOG(DEBUG, "%s get a task", name.c_str());
            //4.处理任务,这个任务属于线程独占的任务
            //t();
            LOG(DEBUG, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());
        }
    }

    int _threadnum;
    std::vector<Thread> _threads;
    std::queue<T> _task_queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    int _waitnum;
    bool _isrunning;

    // 添加单例模式
    static ThreadPool<T> *_instance;
    static pthread_mutex_t _lock;
};

// 在类里面创建的静态变量在类内定义,需要在类外初始化
template <typename T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;

template <typename T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

// 为什么双重判断就可以解决线程安全的问题?
// 为什么static就可以不用创建对象直接调用函数呢?

执行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Canvas与图标】六色彩虹圆角六边形图标

【成图】 120*120的png图标 以下是各种大小图&#xff1a; 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>六色彩虹圆角六边形…

总裁主题CeoMax-Pro主题7.6开心版

激活方式&#xff1a; 1.授权接口源码ceotheme-auth-api.zip搭建一个站点&#xff0c;绑定www.ceotheme.com域名&#xff0c;并配置任意一个域名的 SSL 证书。 2.在 hosts 中添加&#xff1a;127.0.0.1 www.ceotheme.com 3.上传class-wp-http.php到wp-includes目录&#xff…

Java案例——屏蔽信息

首先这次的案例需要用到substring方法&#xff0c;先了解一下&#xff1a; 首先我们来加密一下电话号码&#xff1b; package String; public class Demo_06 {public static void main(String[] args) {// 定义一个电话号码字符串String phoneNumber"13111112598"…

亚信安全DeepSecurity中标知名寿险机构云主机安全项目

近日&#xff0c;亚信安全DeepSecurity成功中标国内知名寿险机构的云主机安全项目。亚信安全凭借在云主机安全防护领域的突出技术优势&#xff0c;结合安全运营的能力&#xff0c;以“实战化”为指导&#xff0c;为用户提供无惧威胁攻击、无忧安全运营的一站式云安全体系&#…

unity中GameObject介绍

在 Unity 中&#xff0c;Cube和Sphere等基本几何体是 Unity 引擎的内置预制体&#xff08;Prefabs&#xff09;&#xff0c;它们属于 Unity 中的GameObject 系统&#xff0c;可以在 Unity 的 Hierarchy 视图或 Scene 视图中右键点击&#xff0c;然后在弹出的菜单中选择 3D Obje…

模型训练识别手写数字(二)

模型训练识别手写数字&#xff08;一&#xff09;使用手写数字图像进行模型测试 一、生成手写数字图像 1. 导入所需库 import cv2 import numpy as np import oscv2用于计算机视觉操作。 numpy用于处理数组和图像数据。 os用于文件和目录操作。 2. 初始化画布 canvas np.z…

多线程——线程的状态

线程状态的意义 ‌线程状态的意义在于描述线程在执行过程中的不同阶段和条件&#xff0c;帮助开发者更好地管理和调度线程资源。 线程的多种状态 线程的状态是一个枚举类型&#xff08;Thread.State&#xff09;&#xff0c;可以通过线程名.getState&#xff08;&#xff09…

(二十一)、Docker 部署 Minikube 使用可视化管理工具 Kuboard

文章目录 1、介绍docker 运行 minikube 集群节点&#xff08;kube-apiserver &#xff09;无法被直接访问的问题Kuboard 需要访问到 k8s 集群的kube-apiserver 2、安装 Kuboard2.1、k8s 集群节点可以被外部直接访问的情况2.1.1、下载镜像2.1.2、运行 deployment.yml2.1.3、访问…

滑动窗口子串

文章目录 滑动窗口一、无重复字符的最长子串二、找到字符串中所有字母异位词 子串三、和为 K 的子数组四、滑动窗口最大值五、最小覆盖子串 滑动窗口 一、无重复字符的最长子串 题目链接 &#xff08;方法一&#xff1a;暴力枚举&#xff09; &#xff08;方法二&#xff…

机器学习—Logistic回归算法

目录 一、基本概念二、决策边界三、损失函数四、交叉熵&#xff08;CrossEntropy&#xff09;损失函数1、二分类问题的交叉熵损失函数2、多分类问题的交叉熵损失函数3、交叉熵损失函数的特点4、交叉熵损失函数的应用 五、模型参数求解六、Logistic函数的应用及优缺点1、应用场景…

【开源免费】基于SpringBoot+Vue.JS在线文档管理系统(JAVA毕业设计)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

开源模型应用落地-Qwen2-VL-7B-Instruct-vLLM-OpenAI API Client调用

一、前言 学习Qwen2-VL &#xff0c;为我们打开了一扇通往先进人工智能技术的大门。让我们能够深入了解当今最前沿的视觉语言模型的工作原理和强大能力。这不仅拓宽了我们的知识视野&#xff0c;更让我们站在科技发展的潮头&#xff0c;紧跟时代的步伐。 Qwen2-VL 具有卓越的图…

C++——string的模拟实现(上)

目录 引言 成员变量 1.基本框架 成员函数 1.构造函数和析构函数 2.拷贝构造函数 3.容量操作函数 3.1 有效长度和容量大小 3.2 容量操作 3.3 访问操作 (1)operator[]函数 (2)iterator迭代器 3.4 修改操作 (1)push_back()和append() (2)operator函数 引言 在 C—…

直播系统源码技术搭建部署流程及配置步骤

系统环境要求 PHP版本&#xff1a;5.6、7.3 Mysql版本&#xff1a;5.6&#xff0c;5.7需要关闭严格模式 Nginx&#xff1a;任何版本 Redis&#xff1a;需要给所有PHP版本安装Redis扩展&#xff0c;不需要设置Redis密码 最好使用面板安装&#xff1a;宝塔面板 - 简单好用的…

深度学习中的迁移学习:优化训练流程与提高模型性能的策略,预训练模型、微调 (Fine-tuning)、特征提取

1024程序员节 | 征文 深度学习中的迁移学习&#xff1a;优化训练流程与提高模型性能的策略 目录 &#x1f3d7;️ 预训练模型&#xff1a;减少训练时间并提高准确性&#x1f504; 微调 (Fine-tuning)&#xff1a;适应新任务的有效方法&#x1f9e9; 特征提取&#xff1a;快速…

AAPL: Adding Attributes to Prompt Learning for Vision-Language Models

文章汇总 当前的问题 1.元标记未能捕获分类的关键语义特征 如下图(a)所示&#xff0c; π \pi π在类聚类方面没有显示出很大的差异&#xff0c;这表明元标记 π \pi π未能捕获分类的关键语义特征。我们进行简单的数据增强后&#xff0c;如图(b)所示&#xff0c;效果也是如…

资讯 | 财富通科技政务协同办公管理软件通过麒麟软件适配认证

2024年9月25日&#xff0c;财富通科技研发的政务协同办公管理软件成功通过中国国产操作系统麒麟软件的适配认证。本次认证是继公司区块链产品“基于区块链的企业及人员资质数字证书服务平台”认证以后得第二次认证。这一成就标志着财富通科技在推动国产软件生态建设方面迈出了坚…

【MySQL基础】数据的增删改查(CRUD)

文章目录 一、 插入数据1. 单条数据插入2. 批量插入数据3. 插入默认值4. 部分字段插入5. 总结 二、更新数据1. 基本的UPDATE语法2. 带多个字段的更新3. 批量条件更新4. 小心条件为空的更新教训 5. 一个实际例子&#xff1a;换专业的情况6. 总结 三、删除数据1. 删除特定数据&am…

基于SSM+微信小程序的社区垃圾回收管理系统(垃圾1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于ssm微信小程序的社区垃圾回收管理系统&#xff0c;有管理员&#xff0c;回收员&#xff0c;用户三个角色。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;回收员管理&am…

java游戏第六天——总结

开始 我们先在这里创建五个java文件&#xff0c;后面创建一个文件夹存储图片&#xff0c;我就按照这几个文件作用展开描述 bg.java package common; import common.game_pic;import java.awt.Color; import java.awt.Graphics; public class bg {public void paintself(Graph…