【Linux】寿司线程池{单例模式之懒汉模式下的线程池}

文章目录

  • 回顾单例模式
  • 0.多线程下的单例模式的意义
  • 1.什么是单例模式
    • 1.0设计模式
    • 1.1C++单例模式的介绍及原理
    • 1.2拷贝构造和赋值重载的处理
    • 1.3if (nullptr == ptr),nullptr放在比较运算符的前面?
    • 1.4实现单例模式的方式
  • 2.实现懒汉方式的单例模式
    • 2.1单线程的单例模式
    • 2.2多线程的单例模式
  • 3.总结线程池
  • 4.代码
    • singleThreadPool.hpp
    • singleTestMain.cc

回顾单例模式

在cpp专栏,我们已经讲过单例模式。特殊类设计[下] — 单例模式

0.多线程下的单例模式的意义

在多线程环境下,单例模式的目的是确保无论有多少线程尝试访问或创建类的实例,都只有一个实例被创建,并且这个实例可以被所有线程共享和访问。这是因为在多线程编程中,如果没有适当的同步机制,多个线程可能同时尝试创建类的实例,从而导致产生多个实例,这与单例模式的初衷相违背。

单例模式在多线程环境下的实现需要特别注意线程安全性。如果没有正确的同步,多个线程可能会同时检查单例是否已经被创建,并且都得出否定的结论,从而都尝试创建新的实例。这会导致产生多个实例,破坏单例模式的约束。

因此,多线程下的单例模式的主要目的是:

确保唯一性:不论何时何地,整个应用程序范围内都只有一个类的实例存在。
线程安全:确保在多线程环境下,类的实例的创建和访问都是安全的,不会出现数据竞争或不一致的情况。
全局访问点:提供一个全局的访问点来获取这个唯一的实例,使得任何需要的地方都可以方便地获取和使用这个实例。
为了实现线程安全的单例模式,通常需要使用互斥锁(如std::mutex)或其他同步机制来确保在创建实例时的线程安全。这样,即使多个线程同时尝试访问或创建单例,也只有一个线程能够成功创建实例,而其他线程将获取已经创建好的实例。

1.什么是单例模式

1.0设计模式

在这里插入图片描述

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

1.1C++单例模式的介绍及原理

单例模式是一种 “经典的, 常用的, 常考的” 设计模式.

单例模式是一种设计模式,它保证一个类仅有一个实例,并提供一个全局访问点来访问该实例。这种模式在许多场景下都很有用,例如,你可能需要一个唯一的配置管理器、资源管理器或线程池。

在C++中,实现单例模式的基本原理如下:

将类的构造函数设为私有,以防止外部代码通过new来创建类的实例。
在类内部提供一个静态的私有成员变量来保存类的唯一实例。
提供一个公开的静态成员函数(通常命名为getInstance或Instance),用于返回类的唯一实例。如果实例尚未创建,该函数将负责创建它。
实现一个简易的单例模式至少要具备的条件

要实现一个简易的单例模式,你需要满足以下条件:

私有构造函数:确保类的实例不能从类外部被创建。
静态私有成员变量:用于存储类的唯一实例。
公开的静态成员函数:用于获取类的唯一实例。这个函数应该在第一次被调用时创建实例,并在后续的调用中返回已创建的实例。
以下是一个简单的C++用懒汉模式的单例模式实现示例:

cpp
class Singleton {  
private:  // 私有构造函数,防止外部创建实例  Singleton() {}  // 静态私有成员变量,保存唯一实例  static Singleton* instance;  public:  // 公开的静态成员函数,用于获取唯一实例  static Singleton* getInstance() {  if (!instance) {  instance = new Singleton();  }  return instance;  }  // 假设的某个成员函数  void someFunction() {  // ...  }  // 析构函数设为公有,但通常不需要外部直接调用  ~Singleton() {  // 清理资源  }  
};  
// 初始化静态成员变量  
Singleton* Singleton::instance = nullptr;

在这个示例中,Singleton类只有一个私有构造函数,因此不能从类外部创建其实例。getInstance函数是获取Singleton类唯一实例的入口点。如果instance为nullptr(即尚未创建实例),则getInstance会创建一个新的实例。在后续调用中,getInstance将直接返回已创建的实例。

注意:上述实现不是线程安全的。在多线程环境下,多个线程可能同时检查instance是否为nullptr并尝试创建实例,从而导致创建多个实例。为了解决这个问题,可以使用互斥锁(如std::mutex)或其他同步机制来确保在任意时刻只有一个线程可以执行创建实例的代码。此外,为了避免内存泄漏,通常还需要提供一个全局的清理函数来删除单例实例。

1.2拷贝构造和赋值重载的处理

在C++单例模式的实现中,拷贝构造函数和赋值操作符通常是不需要的,因此将它们设为私有并禁止外部使用是一个常见的做法。这是因为单例模式要求一个类只有一个实例,而拷贝构造和赋值操作会允许创建类的多个实例,这与单例模式的原则相违背。

对于拷贝构造函数,你可以这样处理:

cpp
class Singleton {  
private:  // 禁止拷贝构造  Singleton(const Singleton&) = delete;  // ... 其他成员 ...  
};

对于赋值操作符,你也可以禁止它:

cpp
class Singleton {  
private:  // 禁止赋值操作  Singleton& operator=(const Singleton&) = delete;  // ... 其他成员 ...  
};

= delete是C++11引入的特性,用于显式地禁止某个成员函数的使用。编译器将尝试调用这些函数时会产生编译错误。

通过禁止拷贝构造和赋值操作,你确保了单例模式的正确性,因为即使尝试进行拷贝或赋值,编译器也会阻止这样的操作。

此外,对于单例模式来说,还需要考虑线程安全的问题。在多线程环境下,需要确保getInstance函数的线程安全性,以避免多个线程同时创建实例。这通常可以通过使用互斥锁(如std::mutex)或其他同步机制来实现。

最后,为了防止内存泄漏,你可能还需要在程序结束时删除单例实例。这可以通过在单例类中添加一个静态的清理函数来实现,该函数在程序结束时调用单例实例的析构函数。但是,请注意,在现代C++中,通常依赖智能指针(如std::shared_ptr)和程序终止时的全局对象析构顺序来自动管理单例实例的生命周期,而不需要显式地编写清理代码。

1.3if (nullptr == ptr),nullptr放在比较运算符的前面?

在C++中,将nullptr放在比较运算符==或!=的前面是一种良好的编程习惯,尤其是在与指针进行比较时。这样做的好处主要体现在两个方面:

防止误写为赋值运算符:
将nullptr放在前面可以防止你不小心将比较运算符==误写为赋值运算符=。如果nullptr在后面,例如ptr = nullptr,当你意图进行比较而误写为赋值时,编译器不会产生错误,但代码的逻辑会完全改变。这可能导致难以察觉的逻辑错误。通过将nullptr放在前面,你可以更容易地识别和纠正这种错误,因为nullptr = ptr这样的写法在语法上是错误的,编译器会立即报错。

代码一致性和可读性:
将常量放在比较运算符的左边可以增强代码的一致性。在C++中,常量通常应该放在比较运算符的左侧,这样可以使代码看起来更加统一和整洁。此外,这种写法也符合一些编程规范和风格指南,有助于提高代码的可读性。

举个例子,假设你有一个指针ptr,你应该这样写比较语句:

cpp
if (nullptr == ptr) {  // ptr 是 nullptr  
}
而不是这样:cpp
if (ptr == nullptr) {  // ptr 是 nullptr  
}

虽然第二种写法在逻辑上是正确的,但第一种写法提供了额外的安全性,防止了可能的误写。

然而,值得注意的是,现代编译器和IDE通常都有强大的代码检查和重构工具,可以帮助你避免这类错误。因此,即使你将nullptr放在比较运算符的右侧,只要你的编程环境配置得当,也可以有效地避免误写为赋值运算符的问题。不过,遵循将常量放在比较运算符左侧的约定仍然是一个好习惯,因为它可以提高代码的一致性和可读性。

1.4实现单例模式的方式

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

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。不用的时候就准备好了,要用的时候直接拿来用。
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度。用的时候再准备。

2.实现懒汉方式的单例模式

2.1单线程的单例模式

static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}return ptrThreadPool;
}

2.2多线程的单例模式

static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}return ptrThreadPool;
}
  1. 单线程下的单例模式代码拿到多线程场景中,需要考虑的【线程安全问题】只发生在第一次创建单例时,即第一次可能有多个线程在执行if语句,这样就可能有不止一个线程获取到了单例,基于这种情况,我们进行了第一步改版。如下。
static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{{lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;
}
  1. 这样并不完全对。我们还要考虑,第一次的某个线程成功获取了单例,即这个线程拿着指向一个单例对象的ptrThreadPool走了,之后的线程再次调用getThreadPoolInstance接口想获取到这个唯一的单例时,需要申请锁–执行if–if不满足–解锁–返回ptrThreadPool。问题来了,之后的线程虽然没有创建单例但是获取到了这个唯一的单例,即之后的线程可以获取到唯一的单例,多线程下的单例模式似乎已经搞定了。但是,这样会存在大量的申请和释放锁的行为,这毫无意义且浪费资源,即之后的线程在获取单例时,if一定是不满足的,这时压根没必要进行加锁和解锁,但是为了保证第一次创建单例是安全的,加锁的行为必须有,之后的线程申请到了锁那还好,如果申请不到还要等待⇒ 这毫无意义且浪费资源。做进一步优化如下。
static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum){if (nullptr == ptrThreadPool){lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;}

这样写的优势

假定【后续线程】:第一次已经有某个线程创建了单例即单例已存在。那么这样写的优势:有效减少后续线程进行加锁检测的问题,拦截大量后续线程直接访问锁的行为
在这里插入图片描述

3.总结线程池

  1. 设计一个线程池,这个线程池有多个线程在运行,一旦接收到任务,则某个线程就会去获取并执行任务。
  2. 可能有多个线程充当派发任务的一方,这时就引入了【单例模式】版的线程池,旨在让不同的线程共享唯一的一个线程池实例,任务方线程们都向这个单例线程池派发任务。
  3. 上面的代码我们只创建了一个任务派送方【main线程】,但是单例模式我们已经写好了,读者们可以创建多个任务派送方测试单例模式是否正确。
  4. 由于我们设计的主线程是死循环,我们并没有维护单例的回收,单例的回收我们在特殊类设计[下] — 单例模式已经讲过。在这里插入图片描述

4.代码

其余代码与【Linux】认识线程池 AND 手撕线程池(正常版)相同,点击获取即可。

singleThreadPool.hpp

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"const int g_threadNum = 3;template <class T>
class singleThreadPool
{
public:pthread_mutex_t *getMutex(){return &lock;}void waitCond(){pthread_cond_wait(&cond, &lock);}bool isEmpty(){return _taskQueue.empty();}T getTask(){T t = _taskQueue.front();_taskQueue.pop();return t;}static void *startRoutine(void *args){ThreadInfo *td = (ThreadInfo *)args;singleThreadPool<T> *tp = (singleThreadPool<T> *)td->_ptrThreadPool;while (true){T task;{lockGuard lockguard(tp->getMutex());while (tp->isEmpty())tp->waitCond();task = tp->getTask();}task(td->_threadName);}}private:// 构造函数singleThreadPool(int thread_num = g_threadNum): _threadNum(thread_num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);for (int i = 1; i <= _threadNum; i++){// 初始化列表区域 对象还未存在 走到函数块{}内 对象已存在 可以使用this指针_threads.push_back(new Thread(i, startRoutine, this));}}singleThreadPool(const singleThreadPool<T> &copy) = delete;const singleThreadPool<T> &operator=(const singleThreadPool<T> &copy) = delete;public:// 考虑 多线程使用单例 的情况static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum){if (nullptr == ptrThreadPool){lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;}void run(){for (auto &iter : _threads){iter->start();// std::cout << iter->name() << " 启动成功" << std::endl;logMsg(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}void pushTask(const T &task){lockGuard lockguard(&lock);_taskQueue.push(task);pthread_cond_signal(&cond);}~singleThreadPool(){for (auto &iter : _threads){iter->join();delete iter;}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}private:int _threadNum;std::vector<Thread *> _threads;std::queue<T> _taskQueue;pthread_mutex_t lock;pthread_cond_t cond;static singleThreadPool<T> *ptrThreadPool;static pthread_mutex_t mutex;
};// 类内声明 类外初始化
template <typename T>
singleThreadPool<T> *singleThreadPool<T>::ptrThreadPool = nullptr;template <typename T>
pthread_mutex_t singleThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;

singleTestMain.cc

#include <ctime>
#include <cstdlib>
#include <iostream>
#include <unistd.h>#include "singleThreadPool.hpp"
#include "Task.hpp"int main()
{srand((unsigned long)time(nullptr) ^ getpid());// ThreadPool<Task> *tp = new ThreadPool<Task>();// ThreadPool<Task> *tp = ThreadPool<Task>::getThreadPoolInstance();//tp->run();singleThreadPool<Task>::getThreadPoolInstance()->run();while (true){// 生产数据/制作任务 -- 耗费时间int x = rand() % 10 + 1;usleep(1000);int y = rand() % 5 + 1;Task t(x, y, [](int x, int y) -> int{ return x + y; });logMsg(DEBUG, "Main-Pro 发送任务: %d+%d=未知", x, y);// 推送任务到线程池中// tp->pushTask(t);singleThreadPool<Task>::getThreadPoolInstance()->pushTask(t);sleep(1);}return 0;
}

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

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

相关文章

微信小程序(黑马优购:登录)

1.点击结算进行条件判断 user.js //数据 state: () >({ // address: {} address: JSON.parse(uni.getStorageSync(address) || {}), token: }), my-settle.vue computed: { ...mapGetters(m_cart,[checkedCount,total,checkedGoodsAmount]), …

前端工程师————CSS学习

选择器分类 选择器分为基础选择器和复合选择器 基础选择器包括&#xff1a;标签选择器&#xff0c;类选择器&#xff0c;id选择器&#xff0c;通配符选择器标签选择器 类选择器 语法&#xff1a;.类名{属性1&#xff1a; 属性值&#xff1b;} 类名可以随便起 多类名使用方式&am…

从乡间小路到布鲁金斯大道:奥林商学院院长的公路之旅

复旦大学-华盛顿大学EMBA项目&#xff0c;是由复旦大学管理学院与美国华盛顿大学奥林商学院联合开办的EMBA项目。美国华盛顿大学奥林商学院&#xff0c;于去年9月迎来新院长Mike。      今年1月&#xff0c;Mazzeo院长首次到访复旦大学&#xff0c;与校友分享了一场 “经济…

中间件安全(apache、tomcat)

靶场&#xff1a; vulfocus Apache Apache HTTP Server 是美国阿帕奇&#xff08; Apache &#xff09;基金会的一款开源网页服务器。该服务器具有快速、可靠且可通过简单的API进行扩充的特点&#xff0c;发现 Apache HTTP Server 2.4.50 中针对 CVE - 2021 - 41773 的修复…

PHP三种方式读取RSA密钥加解密、签名验签完整教程

目录 第一步、生成公私钥 第二步、三种方式读取RSA密钥 第1种&#xff1a;公私钥弄成一行&#xff0c;必须一行没有空格和换行 第2种&#xff1a;直接复制生成公私钥 第3种;复制密钥存储为.pem文件后缀 第三步、RSA加解密 第四步、RSA签名以及验证签名 第五步、封装完整…

开源知识管理和协作平台:插件丰富,主题精美 | 开源日报 No.209

logseq/logseq Stars: 27.8k License: AGPL-3.0 logseq 是一个注重隐私的开源平台&#xff0c;用于知识管理和协作。 提供强大的知识管理、协作、PDF 标注和任务管理工具支持多种文件格式&#xff0c;包括 Markdown 和 Org-modeWhiteboard 功能可使用空间画布组织想法&#x…

【前端】layui前端框架学习笔记

【前端目录贴】 参考视频:LayUI 参考笔记:https://blog.csdn.net/qq_61313896/category_12432291.html 1.介绍 官网&#xff1a;http://layui.apixx.net/index.html 国人16年开发的框架,拿来即用,门槛低 … 2. LayUi的安装及使用 Layui 是一套开源的 Web UI 组件库&#xff0…

[Python] 如何导出PDF文件中的图片

文章目录 一、背景说明二、代码编写三、问题3.1、如何得到图片的xref&#xff1f;3.2、xref有什么用呢&#xff1f; 四、总结 一、背景说明 最近在看一份pdf的书籍&#xff0c;其中有一些图片绘制地比较出色&#xff0c;所以就打算将其复制出来&#xff0c;以便于在需要的时候…

鸿蒙实战开发-如何使用声明式UI编程框架的基础组件

介绍 在本教程中&#xff0c;我们将通过一个简单的样例&#xff0c;学习如何使用声明式UI编程框架的基础组件。本篇Codelab将会使用Image组件、Slider组件、Text组件共同实现一个可调节的风车动画&#xff0c;实现效果如图所示 相关概念 Text组件&#xff1a;文本组件&#x…

使用虚幻引擎为AR体验提供动力

Powering AR Experiences with Unreal Engine ​​​​​​​ 目录 1. 虚幻引擎概述 2. 虚幻引擎如何为AR体验提供动力 3. 虚幻引擎中AR体验的组成部分是什么&#xff1f; 4. 使用虚幻引擎创建AR体验 5. 虚幻引擎中AR的优化提示 6. 将互动性融入AR与虚幻引擎 7. 在AR中…

故障诊断模型 | 基于LSTM长短期记忆神经网络的滚动轴承故障诊断(Pytorch)

概述 LSTM(Long Short-Term Memory)是一种常用的循环神经网络(RNN),在时间序列数据处理任务中表现优秀,可用于滚动轴承故障诊断。 滚动轴承故障通常会导致振动信号的变化,这些振动信号可以被视为时间序列数据。LSTM能够捕捉时间序列之间的依赖关系,从而对滚动轴承的故…

SQL | SQL 3小时快速入门学习笔记

【【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库】 课程链接 一、SQL 1.资料库管理系统&#xff08;Database Management System&#xff0c;简称DBMS&#xff09; 是整理资料的软件。其主要功能包括数据的存储、检索、更新和删除…

Linux(CentOS 7 )基于git、maven实现springboot自动化部署

前提 1、已安装git、maven、java环境 不清楚的可以看另一篇文章&#xff1a; https://blog.csdn.net/weixin_44646763/article/details/137041469 2、已为项目设置远程 git 仓库 origin (可以通过&#xff1a;git remote add origin https://github.com/xxx/xxx.git设置) 创…

在 KubeSphere 中玩转 KubeEdge 边缘计算

文章目录 一、边缘计算概念二、边缘计算痛点三、边缘计算应用场景四、常见边缘计算框架五、KubeEdge 架构4.1、kubeedge云端4.2、kubeedge边端4.3、安装部署4.3.1、安装 Cloudcore4.3.2、添加边缘节点4.3.3、开启Metrics& 日志 4.4、运行应用 六、云边服务互访6.1、部署 Ed…

LLM--提示词Propmt的概念、作用及如何设计提示词

文章目录 1. 什么是提示词&#xff1f;2. 提示词的作用3. 如何设计提示词&#xff1f;3.1. 提供详细的信息3.2. 指定角色3.3. 使用分隔符和特殊符号3.4. 提供示例3.5. 少量示例的思维链&#xff08;COT&#xff09;模型3.6. 思维树&#xff08;TOT&#xff09;模型3.7. 自洽性 …

论文笔记 - :MonoLSS: Learnable Sample Selection For Monocular 3D Detection

论文笔记✍MonoLSS: Learnable Sample Selection For Monocular 3D Detection &#x1f4dc; Abstract &#x1f528; 主流做法限制 &#xff1a; 以前的工作以启发式的方式使用特征来学习 3D 属性&#xff0c;没有考虑到不适当的特征可能会产生不利影响。 &#x1f528; 本…

C++刷题篇——07检测热点字符

一、题目 二、解题思路 1、使用map&#xff0c;key为元素&#xff0c;value为出现的次数 2、由于sort不适用于map&#xff0c;因此要将map的key、value放到vector中&#xff0c;再对vector排序 3、对map排序&#xff1a;方法1&#xff1a;使用二维数组vector<vector<>…

力扣 1143. 最长公共子序列

题目来源&#xff1a;https://leetcode.cn/problems/longest-common-subsequence/description/ C题解&#xff08;思路来源代码随想录&#xff09;&#xff1a;动态规划。 1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][j]&#xff1a;长度为[0, i - 1]…

R2GenCMN中的Encoder_Decoder结构

R2GenCMN中的 Encoder_Decoder 结构 Encoder_Decoder 结构直接关系到文本的生成&#xff0c;它结构参考的transformer的结构 我们这里主要看代码的实现&#xff0c;从视觉编码器的输出开始 1. 模型结构 首先介绍一下整体结构&#xff0c;这里的baseCMN其实就是一个包装了的T…

编曲知识16:贴唱混音思路 录音 对轨 降噪

贴唱混音思路 录音 对轨 降噪小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_6607f17ae4b092c1684f438a?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 混音思路 贴唱混音、分轨混音 贴唱:由翻唱混音发展而来,指仅处理人声和伴奏…