【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就可以不用创建对象直接调用函数呢?

执行结果:

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

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

相关文章

【 thinkphp8 】00006 启动 内、外置服务器

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【 t…

Linux文件类型和根目录结构

Linux文件类型和根目录结构 1.文件类型 字符文件类型说明~普通文件类似于Windows的记事本d目录文件类似于windows文件夹c字符设备文件串行端口设备&#xff0c;顺序读写&#xff0c;键盘b块设备文件可供存储的接口设备&#xff0c;随机读写&#xff0c;硬盘p管道文件用于进程…

jmeter中请求参数:Parameters、Body Data的区别

使用jmeter发送请求&#xff0c;常常要伴随传递参数。有两种请求参数: Parameters, Body Data, 它们的使用方式有很大不同。 先看下get和post请求的区别。 get请求&#xff1a;顾名思义是从服务器获取资源。 post请求&#xff1a;顾名思义是往服务器提交要处理的数据。 直观…

【算法刷题指南】双指针

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

JavaSE要点 1】Java基础

目录 一、编译和运行 二、JDK,JRE和JVM 三、Java中的基本类型 1. 基本类型的大小 四、JVM内存模型 1. 内存模型 五、JVM虚拟机的组成 1. 虚拟机的5个组成部分 2. 虚拟机栈 3. 栈帧 六、值传递和引用传递 七、变量的默认值 八、String的不可变 九. 包装类和常量池&#xff08;-…

基于SSM的网上购物系统的设计与实现

技术介绍 本系统运用了JSP技术、SSM框架、B/S架构和myspl数据库 MySQL 介绍 MySQL是一种关系型的数据库管理系统&#xff0c;属于Oracle旗下的产品。MySQL的语言是非结构化的&#xff0c;使用的用户可以在数据上进行工作。这个数据库管理系统一经问世就受到了社会的广泛关注…

【WebGis开发 - Cesium】三维可视化项目教程---图层管理拓展图层顺序调整功能

目录 引言一、为什么要开发图层顺序调整功能二、开发思路整理1. 拖拽库方案选择2. cesium图层api查询 三、代码编写1. 编写拖拽组件代码2. 修改原有图层管理代码2.1 图层加载移除的调整2.2 图层顺序与拖拽列表的矛盾 3. 编写图层移动代码 四、总结 引言 本教程主要是围绕Cesium…

如何解决 PyQt5 中使用 QtNetwork后使用pyinstaller 打包后网络请求失败的问题

在使用 PyQt5 开发应用程序时&#xff0c;我遇到一个问题&#xff1a;使用 QtNetwork 进行网络通信&#xff0c;在通过 PyInstaller 打包后&#xff0c;应用程序无法正常进行网络请求。经过一些研究和尝试&#xff0c;我找到了解决方案&#xff0c;并记录如下&#xff1a; 问题…

力扣209-长度最小的子数组-滑动窗口思想

题目 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 1&#xff1a; 输入&am…

TCP单包数据大于1460字节会被拆包的问题

关于TCP单包数据大于1460字节会被拆包的问题 1、问题背景&#xff1a; 最近在用STM32W5500做项目&#xff0c;需要STM32通过TCP协议发送数据到上位机并显示。当数据量小的时候上位机显示正常&#xff0c;一旦数据量大过大上位机就会出现数据丢失的情况&#xff0c;甚至数据直接…

LeetCode392:判断子序列

题目链接&#xff1a;392. 判断子序列 - 力扣&#xff08;LeetCode&#xff09; 代码如下&#xff1a; class Solution { public:bool isSubsequence(string s, string t) {int len1 s.size();int len2 t.size();int result 0;vector<vector<int> > dp(len1 …

通过滑动控制 图片3d(多张视频序列帧图片) 展示

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>控制图片变化</title> <style>img {…

深入理解InnoDB底层原理:从数据结构到逻辑架构

💡 无论你是刚刚踏入编程世界的新人,还是希望进一步提升自己的资深开发者,在这里都能找到适合你的内容。我们共同探讨技术难题,一起进步,携手度过互联网行业的每一个挑战。 📣 如果你觉得我的文章对你有帮助,请不要吝啬你的点赞👍分享💕和评论哦! 让我们一起打造…

ctfshow(259->261)--反序列化漏洞--原生类与更多魔术方法

Web259 进入界面&#xff0c;回显如下&#xff1a; highlight_file(__FILE__);$vip unserialize($_GET[vip]); //vip can get flag one key $vip->getFlag();题干里还提示了网站有一个flag.php界面&#xff0c;源代码如下&#xff1a; $xff explode(,, $_SERVER[HTTP_X…

k8s_Pod健康检查

Kubernetes 3种探针介绍 LivenessProbe&#xff08;存活探针&#xff09; LivenessProbe 用于检查容器是否仍然活着。如果探针检测到容器已经失去响应&#xff0c;Kubernetes 将重启该容器。这通常用来修复由于内部状态错误或死锁引起的程序失效问题。 作用&#xff1a;检测容器…

常见的音视频格式介绍

目录 aac格式介绍&#xff08;ADTS&#xff09;h264格式分析FLV和MP4格式介绍 aac格式介绍&#xff08;ADTS&#xff09; aac的格式有两种&#xff1a;ADIF不常用&#xff0c;ADTS是主流&#xff0c;所以这里主要讲解ADTS。简单来说&#xff0c;ADTS可以在任意帧解码&#xff…

校园表白墙源码修复版

此校园表白墙源码基于thinkphp&#xff0c;因为时代久远有不少bug&#xff0c;经本人修复已去除大部分bug&#xff0c;添加了美化元素。 https://pan.quark.cn/s/1f9b3564c84b https://pan.baidu.com/s/1bb9vu9VV2jJoo9-GF6W3xw?pwd7293 https://caiyun.139.com/m/i?2hoTc…

基于机器学习的个性化电影推荐系统【源码+安装+讲解+售后+文档】

【1】系统介绍 研究背景 随着互联网技术的迅速发展&#xff0c;数字娱乐内容特别是电影和电视剧的数量急剧增加。用户在享受丰富内容的同时&#xff0c;也面临着选择困难的问题&#xff0c;即“信息过载”。传统的搜索和分类方法已经无法满足用户日益增长的个性化需求。与此同…

多款云存储平台存在安全漏洞,影响超2200万用户

据苏黎世联邦理工学院研究人员Jonas Hofmann和Kien Tuong Turong的发现&#xff0c;端到端加密&#xff08;E2EE&#xff09;云存储平台存在一系列安全问题&#xff0c;可能会使用户数据暴露给恶意行为者。在通过密码学分析后&#xff0c;研究人员揭示了Sync、pCloud、Icedrive…

一位Go开发者的深度访谈:从进阶到实战,《Let’s Go Further!》如何开拓Go语言新世界

读者&#xff1a;我最近在学习Go&#xff0c;发现构建API和Web应用方面还是有很多挑战&#xff0c;尤其是需要兼顾代码架构、性能和扩展性。您作为一位Go语言技术专家&#xff0c;怎么看《Let’s Go Further!》这本书呢&#xff1f; 专家&#xff1a;这本书确实很有意思。《Le…