1. 单例模式

单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问、管理共享状态或协调系统行为时非常有用。

单例模式的核心特点

  • 私有构造函数:防止外部通过new关键字创建实例;
  • 静态成员函数:提供全局访问点(静态成员函数getInstance());
  • 禁止拷贝和赋值:通过delete关键字禁用拷贝构造函数和赋值运算符。

单例模式的应用场景

  • 日志系统:确保所有日志都写入同一个日志文件;
  • 配置管理:全局共享一份配置信息;
  • 数据库连接池:统一管理数据库连接;
  • 设备管理器:如打印机等硬件设备的管理。

需要注意的是,单例模式虽然方便,但也会带来一些问题,如增加代码耦合度、不利于单元测试等,因此在使用时需要权衡利弊。

在 C++ 中,单例模式主要有两种实现方式:饿汉式懒汉式,它们的核心区别在于实例创建的时机不同

1.1 饿汉式实现

饿汉式在程序启动时(类加载时)就创建唯一实例,不管后续是否会使用到。

class EagerSingleton {
private:// 私有构造函数,防止外部创建实例EagerSingleton() {}// 禁用拷贝构造和赋值运算EagerSingleton(const EagerSingleton&) = delete;EagerSingleton& operator=(const EagerSingleton&) = delete;// 静态成员变量,程序启动时就初始化static EagerSingleton instance;public:// 提供全局访问点static EagerSingleton& getInstance() {return instance;}
};// 在类外初始化静态成员
EagerSingleton EagerSingleton::instance;

饿汉式特点:

  • 线程安全:由于实例在程序启动时就创建,不存在多线程竞争问题;
  • 可能浪费资源:如果单例从未被使用,也会占用内存;
  • 实现简单:不需要考虑线程同步问题。

1.2 懒汉式实现

懒汉式在第一次调用getInstance()方法时才创建实例,实现了 "延迟初始化"

class LazySingleton {
private:// 私有构造函数LazySingleton() {}// 禁用拷贝构造和赋值运算LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;public:// 提供全局访问点,C++11后局部静态变量初始化是线程安全的static LazySingleton& getInstance() {// 第一次调用时才初始化实例static LazySingleton instance;return instance;}
};

或者:

class LazySingleton {
private:// 私有构造函数LazySingleton() {}// 私有析构函数~LazySingleton() {instance = nullptr;}// 禁用拷贝构造和赋值运算LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;// 使用指针的方式来访问唯一实例static LazySingleton *instance;public:// 此方式无法保证线程安全,应当加锁保护static LazySingleton* const getInstance() {// 第一次调用时才初始化实例if(instance == nullptr)instance = new LazySingleton();return instance;}
};LazySingleton* LazySingleton::instance = nullptr;

前者和饿汉式一样,实例在创建之后就不会消失后者的实例则可以在被销毁之后重新申请

懒汉式特点:

  • 延迟初始化:节省资源,只有在真正需要时才创建实例;
  • 线程安全(C++11 及以上):标准保证局部静态变量的初始化是线程安全的;
  • 可能影响首次访问性能:第一次调用时需要完成初始化。

1.3 总结

饿汉式懒汉式
实例创建时机程序启动时首次使用时
线程安全性天然安全C++11 后安全
资源利用可能浪费更高效
实现复杂度简单稍复杂(需考虑线程安全)

实际开发中,懒汉式因为其资源利用效率更高而更常用,尤其是在单例可能不会被使用的场景下。而饿汉式适合在程序启动时就需要初始化的核心组件。

2. 用单例模式设计线程池

2.1 什么是线程池

线程池是一种线程管理机制,它预先创建一定数量的线程,通过复用这些线程来处理多个任务,从而避免频繁创建和销毁线程带来的性能开销,提高系统效率和资源利用率

线程池包含一个线程队列和一个任务队列

  • 线程队列:存储预先创建的空闲线程,等待处理任务。
  • 任务队列:存放待执行的任务,当线程空闲时会从队列中获取任务执行。

2.2 如何将任务传递给线程

首先,任务队列一定是定义在线程池内部的,我们要使线程访问到任务队列及保护任务队列的锁,就要使这些线程能访问到线程池本身。

所以,我们在线程池内部定义线程的运行函数,即不断循环访问任务队列获取任务:

void ThreadHandler()
{while (true){Task task;{LockGuard lockguard(_queue_mutex);while (_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if (_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "线程[" << Thread::GetMyName() << "]退出...";
}

但是直接将该函数传递给线程是不行的。例如,在构造线程时,直接将该函数以及this传过去:

ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true)
{if (thread_num <= 0)throw ThreadPoolException("线程数量过少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), ThreadHandler, this);
}

编译就会出现如下报错:

这个编译错误的核心原因是:非静态成员函数(ThreadHandler)不能直接作为函数名传递给线程构造函数。非静态成员函数的调用必须依赖于一个类的实例(this指针),而直接进行传参时,编译器无法自动关联实例,因此判定为 “无效使用非静态成员函数”。

让线程执行非静态成员函数的正确方式是使用lambda表达式捕捉this并包装该函数:

[this] { ThreadHandler(); 
}

然后像这样传参:

_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });

2.3 懒汉实现1

#pragma once
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Log.hpp"using namespace MutexModule;
using namespace ThreadModule;
using namespace CondModule;
using namespace LogModule;namespace ThreadPoolModule
{class ThreadPoolException : public std::runtime_error{public:explicit ThreadPoolException(const std::string &message): std::runtime_error("ThreadPoolException: " + message) {}};static const unsigned int default_thread_num = 6;static const unsigned int default_capacity = 9;template <typename Task>class ThreadPool{private:ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true){if (thread_num <= 0)throw ThreadPoolException("线程数量过少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });}~ThreadPool(){}ThreadPool(const ThreadPool<Task>&) = delete;ThreadPool<Task>& operator=(const ThreadPool<Task>&) = delete;void ThreadHandler(){while (true){Task task;{LockGuard lockguard(_queue_mutex);while(_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if(_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "线程[" << Thread::GetMyName() << "]退出...";}void Start(){for (auto &thread : _threads)thread.Start();LOG(LogLevel::DEBUG) << "线程池已启动...";}public:void Stop(){_isrunning = false;_not_empty.broadcast();LOG(LogLevel::DEBUG) << "线程池开始停止...";}void Wait(){if(_isrunning)throw ThreadPoolException("等待前线程池未停止!");for (auto &thread : _threads)thread.Join();LOG(LogLevel::DEBUG) << "等待成功, 线程已全部退出...";}void PushTask(const Task &task){if(!_isrunning)throw ThreadPoolException("线程池已停止, 无法继续增加任务!");_queue.push(task);_not_empty.signal();}static ThreadPool<Task>& GetInstance(){static ThreadPool<Task> Instance;Instance.Start();return Instance;}private:bool _isrunning;std::vector<Thread> _threads;std::queue<Task> _queue;Mutex _queue_mutex;Cond _not_empty;};
}

2.4 懒汉实现2

#pragma once
#include <vector>
#include <queue>
#include "Mutex.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Log.hpp"using namespace MutexModule;
using namespace ThreadModule;
using namespace CondModule;
using namespace LogModule;namespace ThreadPoolModule
{class ThreadPoolException : public std::runtime_error{public:explicit ThreadPoolException(const std::string &message): std::runtime_error("ThreadPoolException: " + message) {}};static const unsigned int default_thread_num = 6;static const unsigned int default_capacity = 9;template <typename Task>class ThreadPool{private:ThreadPool(unsigned int thread_num = default_thread_num): _isrunning(true){if (thread_num <= 0)throw ThreadPoolException("线程数量过少!");for (int i = 1; i <= thread_num; i++)_threads.emplace_back("thread-" + std::to_string(i), [this]{ ThreadHandler(); });}~ThreadPool(){_ins = nullptr;}ThreadPool(const ThreadPool<Task>&) = delete;ThreadPool<Task>& operator=(const ThreadPool<Task>&) = delete;void ThreadHandler(){while (true){Task task;{LockGuard lockguard(_queue_mutex);while(_queue.empty() && _isrunning){_not_empty.wait(_queue_mutex);}if(_queue.empty() && !_isrunning)break;task = _queue.front();_queue.pop();}task();}LOG(LogLevel::DEBUG) << "线程[" << Thread::GetMyName() << "]退出...";}void Start(){for (auto &thread : _threads)thread.Start();LOG(LogLevel::DEBUG) << "线程池已启动...";}public:void Stop(){_isrunning = false;_not_empty.broadcast();LOG(LogLevel::DEBUG) << "线程池开始停止...";}void Wait(){if(_isrunning)throw ThreadPoolException("等待前线程池未停止!");for (auto &thread : _threads)thread.Join();delete _ins;LOG(LogLevel::DEBUG) << "等待成功, 线程已全部退出...";}void PushTask(const Task &task){if(!_isrunning)throw ThreadPoolException("线程池已停止, 无法继续增加任务!");_queue.push(task);_not_empty.signal();}static ThreadPool<Task> *const GetInstance(){if (_ins == nullptr){LockGuard lockguard(_mutex);if (_ins == nullptr){try{_ins = new ThreadPool<Task>();}catch (const std::exception &e){std::cerr << e.what() << '\n';}_ins->Start();}}return _ins;}private:bool _isrunning;std::vector<Thread> _threads;std::queue<Task> _queue;static ThreadPool<Task> *_ins;Mutex _queue_mutex;static Mutex _mutex;Cond _not_empty;};template <typename Task>ThreadPool<Task> *ThreadPool<Task>::_ins = nullptr;template <typename Task>Mutex ThreadPool<Task>::_mutex;
}

相比较于第一种,第二种可以在调用Stop以及Wait函数之后重新启动。

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

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

相关文章

Linux中的指令

1.adduseradduser的作用是创立一个新的用户。当我们在命令行中输入1中的指令后&#xff0c;就会弹出2中的命令行&#xff0c;让我们设立新的密码&#xff0c;紧接着就会让我们再次输入新的密码&#xff0c;对于密码的输入它是不会显示出来的&#xff0c;如果输入错误就会让我们…

【n8n】Docker容器中安装ffmpeg

容器化部署 n8n 时&#xff0c;常常会遇到一些环境依赖问题。缺少 docker 命令或无法安装 ffmpeg 是较为常见的场景&#xff0c;如果处理不当&#xff0c;会导致流程执行受限。 本文介绍如何在 n8n 容器中解决 docker 命令不可用和 ffmpeg 安装受限的问题&#xff0c;并给出多…

【基础算法】初识搜索:递归型枚举与回溯剪枝

文章目录一、搜索1. 什么是搜索&#xff1f;2. 遍历 vs 搜索3. 回溯与剪枝二、OJ 练习1. 枚举子集 ⭐(1) 解题思路(2) 代码实现2. 组合型枚举 ⭐(1) 解题思路请添加图片描述(2) 代码实现3. 枚举排列 ⭐(1) 解题思路(2) 代码实现4. 全排列问题 ⭐(1) 解题思路(2) 代码实现一、搜…

Node.js异步编程——async/await实现

一、async/await基础语法 在Node.Js编程中,async关键字用于定义异步函数,这个异步函数执行完会返回一个Promise对象,异步函数的内部可以使用await关键字来暂停当前代码的继续执行,直到Promise操作完成。 在用法上,async关键字主要用于声明一个异步函数,await关键字主要…

搭建一个简单的Agent

准备本案例使用deepseek&#xff0c;登录deepseek官网&#xff0c;登录账号&#xff0c;充值几块钱&#xff0c;然后创建Api key可以创建虚拟环境&#xff0c;python版本最好是3.12&#xff0c;以下是文件目录。test文件夹中&#xff0c;放一些txt文件做测试&#xff0c;main.p…

uv,下一代Python包管理工具

什么是uv uv&#xff08;Universal Virtual&#xff09;是由Astral团队&#xff08;知名Python工具Ruff的开发者&#xff09;推出的下一代Python包管理工具&#xff0c;使用Rust编写。它集成了包管理、虚拟环境、依赖解析、Python版本控制等功能&#xff0c;它聚焦于三个关键点…

单片机的输出模式推挽和开漏如何选择呢?

推挽和开漏是单片机的输出模式&#xff0c;属于I/O口配置的常见类型。开漏&#xff08;Open-Drain&#xff09;和推挽&#xff08;Push-Pull&#xff09;是两种根本不同的输出电路结构&#xff0c;理解它们的区别是正确使用任何单片机&#xff08;包括51和STM32&#xff09;GPI…

java18学习笔记-Simple Web Server

408:Simple Web Server Python、Ruby、PHP、Erlang 和许多其他平台提供从命令行运行的开箱即用服务器。这种现有的替代方案表明了对此类工具的公认需求。 提供一个命令行工具来启动仅提供静态文件的最小web服务器。没有CGI或类似servlet的功能可用。该工具将用于原型设计、即…

深度解析Atlassian 团队协作套件(Jira、Confluence、Loom、Rovo)如何赋能全球分布式团队协作

无穷无尽的聊天记录、混乱不堪的文档、反馈信息分散在各个不同时区……在全球分布式团队中开展真正的高效协作&#xff0c;就像是一场不可能完成的任务。 为什么会这样&#xff1f;因为即使是最聪明的团队&#xff0c;也会遇到类似的障碍&#xff1a; 割裂的工作流&#xff1a…

理解AI 智能体:智能体架构

1. 引言 智能体架构&#xff08;agent architecture&#xff09;是一份蓝图&#xff0c;它定义了AI智能体各组件的组织方式和交互机制&#xff0c;使智能体能够感知环境、进行推理并采取行动。本质上&#xff0c;它就像是智能体的数字大脑——整合了“眼睛”&#xff08;传感器…

Spring Cloud系列—SkyWalking链路追踪

上篇文章&#xff1a; Spring Cloud系列—Seata分布式事务解决方案TCC模式和Saga模式https://blog.csdn.net/sniper_fandc/article/details/149947829?fromshareblogdetail&sharetypeblogdetail&sharerId149947829&sharereferPC&sharesourcesniper_fandc&…

机器人领域的算法研发

研究生期间学习大模型&#xff0c;可投递机器人领域的算法研发、技术支持等相关岗位&#xff0c;以下是具体推荐&#xff1a; AI算法工程师&#xff08;大模型方向-机器人应用&#xff09;&#xff1a;主要负责大模型开发与优化&#xff0c;如模型预训练、调优及训练效率提升等…

深度学习入门:神经网络

文章目录一、深度学习基础认知二、神经网络核心构造解析2.1 神经元的基本原理2.2 感知器&#xff1a;最简单的神经网络2.3 多层感知器&#xff1a;引入隐藏层解决非线性问题2.3.1 多层感知器的结构特点2.3.2 偏置节点的作用2.3.3 多层感知器的计算过程三、神经网络训练核心方法…

mysql的索引有哪些?

1. 主键索引&#xff08;PRIMARY KEY&#xff09;主键索引通常在创建表时定义&#xff0c;确保字段唯一且非空&#xff1a;-- 建表时直接定义主键 CREATE TABLE users (id INT NOT NULL,name VARCHAR(50),PRIMARY KEY (id) -- 单字段主键 );-- 复合主键&#xff08;多字段组合…

【计算机视觉与深度学习实战】08基于DCT、DFT和DWT的图像变换处理系统设计与实现(有完整代码python3.13可直接粘贴使用)

1. 引言 数字图像处理作为计算机视觉和信号处理领域的重要分支,在过去几十年中得到了快速发展。图像变换技术作为数字图像处理的核心技术之一,为图像压缩、特征提取、去噪和增强等应用提供了强有力的数学工具。离散余弦变换(Discrete Cosine Transform, DCT)、离散傅里叶变…

使用Python实现DLT645-2007智能电表协议

文章目录&#x1f334;通讯支持&#x1f334; 功能完成情况服务端架构设计一、核心模块划分二、数据层定义三、协议解析层四、通信业务层&#xff08;以DLT645服务端为例&#xff09;五、通信层&#xff08;以TCP为例&#xff09;使用例子&#x1f334;通讯支持 功能状态TCP客…

未来已来:基于IPv6单栈隔离架构的安全互联实践报告

未来已来&#xff1a;基于IPv6单栈隔离架构的安全互联实践报告 报告摘要 随着IPv4地址资源彻底枯竭&#xff0c;全球网络基础设施正加速向IPv6单栈&#xff08;IPv6-Only&#xff09;演进。传统“IPv4为主、IPv6为辅”的双栈模式已无法满足数字化转型对海量地址、端到端连接与原…

Ubuntu24.04 安装 Zabbix

Ubuntu24.04 安装 Zabbix 环境&#xff1a; 软件版本Ubuntu24.04.3Nginx1.24.0MySQL8.4.6PHP8.3.6phpMyAdmin5.2.2Zabbix7.4.1 LNMP 1. 更新本地软件包索引并升级已安装软件 更新可用软件包列表 把已安装的软件升级到最新版 安装常用工具 sudo apt update && sud…

【动手学深度学习】6.2. 图像卷积

目录6.2. 图像卷积1&#xff09;互相关运算2&#xff09;卷积层3&#xff09;图像中目标的边缘检测4&#xff09;学习卷积核5&#xff09;互相关与卷积6&#xff09;特征映射和感受野7&#xff09;小结. 6.2. 图像卷积 卷积神经网络的设计是用于探索图像数据&#xff0c;本节…

游戏引擎中的Billboard技术

一.视觉公告板为解决场景中Mesh网格面数过多问题,使用2D平面Mesh替换为3D平面Mesh的技术即为Billboard技术.常用于场景中植被,树叶,粒子系统等对面数有要求的场景.二.Billboard着色器实现着色器输入参数:摄像机坐标,网格坐标,摄像机观察方向着色器输出:实际2D平面随视角不变