当前位置: 首页 > news >正文

线程池和单例模式

一. 线程池

1. 解决了那些问题

1.1 降低资源消耗(解决创建/销毁线程的高成本问题)

线程的创建会消耗CPU资源(分配创建新的TCB,分配独立的栈空间,继承主线程的页表和文件描述,添加至调度队列,寄存器中设置独立上下文),线程的销毁亦是。所以频繁的创建和销毁会大幅度降低CPU的性能。

而线程池避免了频繁的创建和销毁,创建一定数量的线程进行复用,尤其适合短期任务频繁执行的场景

  1. TCB分配与初始化:这不仅仅是分配一小块内存。task_struct 是一个极其庞大的结构体(在最新内核中可能有几KB大小),初始化其数十个字段本身就是不小的计算任务。

  2. 栈空间分配:虽然分配虚拟地址空间本身很快,但物理内存的分配是“按需”通过页故障实现的。新线程开始运行时,会触发一系列的缺页中断,这会导致内核陷入、分配物理页、更新页表,成本很高。

  3. 调度器开销

    • 队列操作:将线程加入调度队列需要锁来保护队列的一致性,这在多核环境下可能引发锁竞争

    • 缓存失效:当一个新线程被调度到CPU上时,它几乎没有任何数据在CPU的高速缓存中(即“冷缓存”)。这会导致它开始运行的初期性能很差,直到慢慢将所需数据加载到缓存中。同时,它还可能“挤掉”其他线程的热数据,导致系统整体缓存效率下降。

1.2 控制并发线程数量,避免资源耗尽

CPU资源耗尽

  • 过度的上下文切换:大量可运行线程会导致操作系统花费大量时间在线程切换上,而不是执行实际任务。这会显著降低吞吐量,CPU时间都被浪费在“管理”线程上了。

  • 缓存抖动:频繁切换线程,会导致CPU的各级缓存(L1/L2/L3)频繁失效,每个线程都需要加载自己的资源,导致缓存命中率急剧下降。

内存资源耗尽

  • 每个线程都需要分配独立的栈空间(默认从几KB到几MB不等)。1000个线程就可能占用数GB的虚拟内存,并消耗大量物理内存作为后备存储。

  • 最终导致内存严重不足,OS杀死进程(触发linux的OOM Killer),或者进行频繁切换线程,导致OS近乎停滞

其他资源的耗尽

  • 文件描述符:每个线程可能打开文件、网络连接,耗尽系统允许的最大文件描述符数量。

  • 网络带宽/连接数: 过多并发的发送网络请求,会导致自身或对方无法承受

  • 数据库连接: 耗尽连接池,导致无法连接或数据库响应缓慢

1.3 提高任务响应速度

线程池中的线程处于就绪态,当有新任务的时候,无需等待线程创建,而是直接分配线程去执行,减少任务启动延迟。

1.4 统一管理和监控线程资源

线程池提供了对线程的集中管理能力:

  • 控制任务队列长度(当线程都在忙碌时,任务可暂存于队列,避免任务丢失)。
  • 支持线程池参数的动态调整(如核心线程数,最大线程数)。
  • 监控任务完成情况(如完成数,失败数,执行时间等),便于问题排斥和性能提升。

1.5 避免线程泄漏

若手动管理线程时未及时回收(如任务执行异常结束,导致线程未释放),可能造成的内存泄漏,长期如此会导致系统资源耗尽,线程池通过内部机制保证线程的生命周期管理。

2. 实现思路

线程池的主要作用是为了管理任务队列,复用线程,控制并发量,其思路可以拆分为以下几个关键步骤和组件:

2.1.核心组件定义:

  • 线程池管理器:负责线程池的创建、销毁、参数配置(如核心线程数、最大线程数等)。
  • 工作线程(Worker):预先创建线程进行线程等待,循环从任务队列中提取任务并执行。
  • 任务队列(Task Queue):存错待执行的任务(通常是阻塞队列,避免线程空轮询)。
  • 任务接口(Runnable/Callable):定义任务的执行逻辑,线程池执行并接受任务的接口。

2.2.核心参数设计

线程池的行为由一些参数控制,需要初始化时定义:

  • 核心线程数(corePoolSize):线程池长期维持的最小线程数(即使空闲也不销毁)。
  • 最大线程数(maximunPoolSize): 线程池中最多可存在线程数(核心线程忙时,可临时扩大至此)。
  • 空闲线程可存活时间(KeepAliveTime):超过核心线程数的临时现场,空闲时存在最长时间,过后销毁。
  • 任务队列(WorkQueue):用于缓存任务的阻塞队列。
  • 拒绝策略(RejectedExecutionHandler):当任务队列满且线程数达到最大值时,处理新任务的策略(如丢弃,抛出异常,让提交者执行等)

2.3. 核心流程实现

(1)初始化线程池
  • 启动时根据corePoolSize创建核心线程,这些线程进入阻塞等待状态(通过阻塞队列的take()方法等待任务)。
  • 初始化任务队列和拒绝策略。
(2)提交任务的处理逻辑

当外部提交任务时,线程池按以下逻辑处理:

  1. 若当前线程数 < 核心线程数:直接创建新线程执行任务(即使有其他核心线程空闲,也会优先新建到核心数)。
  2. 若当前线程数 ≥ 核心线程数:将任务加入任务队列,等待空闲线程从队列中获取执行。
  3. 若任务队列已满
    • 若当前线程数 < 最大线程数:创建临时线程执行任务(临时线程空闲后会按keepAliveTime销毁)。
    • 若当前线程数 ≥ 最大线程数:触发拒绝策略处理新任务。
(3)工作线程的运行逻辑

每个工作线程(Worker)是一个循环体,逻辑如下:

// 伪代码示意
while (true) {Task task = workQueue.take(); // 阻塞等待队列中的任务try {task.run(); // 执行任务} catch (Exception e) {// 处理任务执行异常}
}
  • 核心线程:即使任务队列空,也会一直阻塞等待新任务(不会被销毁)。
  • 临时线程:若空闲时间超过keepAliveTime,则退出循环并销毁。
(4)线程池的销毁
  • 调用销毁方法(如shutdown())时,线程池不再接收新任务,等待已提交的任务执行完毕后,中断所有工作线程,释放资源。

2.4. 关键技术点

  • 阻塞队列:使用阻塞队列(如BlockingQueue)实现任务的缓存和线程间的同步,避免线程空轮询(通过take()阻塞等待)。
  • 线程复用:工作线程通过循环从队列取任务,避免了频繁创建 / 销毁线程的开销。
  • 并发控制:通过锁(如ReentrantLock)或原子操作保证线程池状态(如运行、关闭)和参数修改的线程安全。
  • 动态调整:支持运行时调整核心线程数、最大线程数等参数(需配合锁保证线程安全)。

3. 代码示例(简易)

cond.hpp

#pragma once
#include "Mutex.hpp"namespace CondModule
{class Cond{public:Cond(){std::cout << " Cond()" << std::endl;pthread_cond_init(&_cond, NULL);}~Cond(){std::cout << " ~Cond()" << std::endl;pthread_cond_destroy(&_cond);}void Wait(MutexModule::Mutex& mutex){std::cout << " Wait(MutexModule::Mutex& mutex)" << std::endl;pthread_cond_wait(&_cond, mutex.GetMutex());}void Singel(){pthread_cond_signal(&_cond);}void Broadcast(){pthread_cond_broadcast(&_cond);}private:pthread_cond_t _cond;};
};

Mutex.hpp

#pragma once#include <pthread.h>namespace MutexModule
{// using namespace LogModule;class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, NULL);}~Mutex(){pthread_mutex_destroy(&_mutex);}void Lock(){int n = pthread_mutex_lock(&_mutex);if(n != 0){return;}}void UnLock(){int n = pthread_mutex_unlock(&_mutex);if(n != 0){return;}}pthread_mutex_t* GetMutex(){return &_mutex;}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex& mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.UnLock();}private:Mutex& _mutex;};
}

pthread.hpp

#include <pthread.h>
#include <string>
#include <functional>
#include <atomic>#include "Log.hpp"namespace PthreadModule
{namespace{static std::atomic<int> number{0};//原子计数器}class Thread{using fun_t = std::function<void()>;private:void Enabled_Detach(){_isDetach = true;}void Enabled_Run(){_isRun = true;}static void* Routine(void* args)// 类中存在this指针,void*(*)(void*) 签名不匹配{Thread* self = static_cast<Thread*>(args);self->Enabled_Run();self->Enabled_Detach();self->Detach();pthread_setname_np(self->_tid, self->_name.c_str());self->_fun();return (void*)0;}public:Thread(fun_t fun):_tid(0),_isDetach(false),_isRun(false),_fun(fun){_name = "pthread-" + std::to_string(number++);}bool start(){if(_isRun)return false;int ret = pthread_create(&_tid, NULL, Routine, this);if(ret == 0){LogModule::LOG(LogModule::LogLevel::INFO) << "create success!";return true;}else{LogModule::LOG(LogModule::LogLevel::ERROR) << "create success!";return false;}}void stop(){if(_isRun){int ret = pthread_cancel(_tid);LogModule::LOG(LogModule::LogLevel::INFO) << "pthread Has been cancelled";_isRun = false;(void)ret;}else{LogModule::LOG(LogModule::LogLevel::INFO) << "pthread Not in operation";}}void Detach() {if(_isDetach && _isRun){// LogModule::LOG(LogModule::LogLevel::INFO) << "已调用";int ret = pthread_detach(_tid);(void)ret;}}void Jion(){if(_isDetach) return;int ret =pthread_join(_tid, NULL);(void)ret;}const std::string Name(){return _name;}~Thread(){}private:pthread_t _tid;std::string _name;bool _isDetach;bool _isRun;fun_t _fun;};
}

以上代码可直接替换为C++类部实现方式

log.hpp

#pragma once#include <iostream>
#include <filesystem>
#include <string>
#include <memory>
#include <sstream>
#include <unistd.h>
#include <ctime>
#include <fstream> 
#include "Mutex.hpp"namespace LogModule
{using namespace std;// 日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL,};// 获取日志等级字符串string LevelToString(LogLevel level){switch(level){case LogLevel::DEBUG:    return "DEBUG";case LogLevel::INFO:     return "INFO";case LogLevel::WARNING:  return "WARNING";case LogLevel::ERROR:    return "ERROR";case LogLevel::FATAL:    return "FATAL";default:                 return "UNKNOWN";}}// 获取时间字符串string GetCurrentTime(){time_t curTime = time(nullptr);struct tm currTm;char timeStr[128] = {0};localtime_r(&curTime, &currTm);snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d", currTm.tm_year + 1900, currTm.tm_mon + 1, currTm.tm_mday,currTm.tm_hour,currTm.tm_min,currTm.tm_sec);return timeStr;}// 日志策略基类class LogStrategy{public:virtual ~LogStrategy() = default;virtual void Log(const string& message) = 0; //纯虚函数};// 终端打印策略class TerminalPrintStrategy : public LogStrategy {public:void Log(const string& message) override{MutexModule::LockGuard lock(_mutex);cout << message << endl;}private:MutexModule::Mutex _mutex;};class FilePrintStrategy : public LogStrategy{public:FilePrintStrategy():_path("./log"),_file("log.log"){MutexModule::LockGuard lock(_mutex);if(filesystem::exists(_path)){return;}try{filesystem::create_directory(_path);}catch(const filesystem::filesystem_error &err){cout << err.what() << endl;}}~FilePrintStrategy(){}void Log(const string& message) override{MutexModule::LockGuard lock(_mutex);string filename = _path + (_path.back() == '/' ?  "" :  "/") + _file;ofstream out(filename, ios::app);//以追加方式打开if(!out.is_open()) return;out << message << "\r\n";out.close();}private:string _path;string _file;MutexModule::Mutex _mutex;};// 日志器class Logger{public:Logger(){EnableTerminalOutput();}void EnableTerminalOutput(){_strategy = make_unique<TerminalPrintStrategy>();}void EnableFileOutput(){_strategy = make_unique<FilePrintStrategy>();}class LogMessage{public:LogMessage(LogLevel level, const string& srcName, int line, Logger& logger): _level(level),_logger(logger){stringstream header;header << "\033[1;32m[" << GetCurrentTime() << "]"<< "[" << srcName << ":" << line << ":" << getpid() << ":" << LevelToString(level) << "]\033[0m ";_logMessage = header.str();}template <typename T>LogMessage& operator<<(const T& value){stringstream ss;ss << value;_logMessage += ss.str();return *this;}~LogMessage(){if(_logger._strategy){_logger._strategy->Log(_logMessage);}}private:LogLevel _level;string _logMessage;Logger& _logger;};LogMessage operator()(LogLevel level, const string& srcName, int line){return LogMessage(level, srcName, line, *this);}private:unique_ptr<LogStrategy> _strategy;};// 全局日志器实例Logger logger;// 日志宏#define LOG(level) logger(level, __FILE__, __LINE__)#define Enable_Terminal logger.EnableTerminalOutput()#define Enable_File logger.EnableFileOutput()
}

pthreadPool.hpp

#include <vector>
#include <queue>#include "Pthread.hpp"
#include "Cond.hpp"namespace ThreadPoolModule
{using namespace PthreadModule;using namespace MutexModule;using namespace CondModule;using namespace LogModule;static const int _default = 2;template <typename T>class ThreadPool{private:void wakeUpOne(){_cond.Singel();}void wakeUpAll(){LockGuard lock(_mutex);if(_IsSleepNum){_cond.Broadcast();}_IsSleepNum = 0;}ThreadPool(int num = _default):_num(num),_IsSleepNum(0),_Isrun(false){for(int i = 0; i < _num; i++){_threads.emplace_back([this](){HandlerTask();});//容器中构造元素(避免临时对象拷贝)}}~ThreadPool(){}void start(){if(_Isrun) return;_Isrun = true;for(auto& th : _threads){bool ret = th.start();LOG(LogModule::LogLevel::INFO) << th.Name();(void)ret;}}public:void stop() {if (!_Isrun) return;{LockGuard lock(_mutex);_Isrun = false;_cond.Broadcast();  // 唤醒所有线程}for (auto& th : _threads) {th.stop();  // 确保调用 Thread 的停止逻辑}}void HandlerTask(){char pthreadName[64] = {0};pthread_getname_np(pthread_self(), pthreadName, sizeof(pthreadName));T t;while(1){{LockGuard lock(_mutex);while(_Isrun && _tasks.empty())//使用while检测,防止线程虚假唤醒{// LOG(LogModule::LogLevel::INFO) << pthreadName << "无任务"<<_IsSleepNum;++_IsSleepNum;LOG(LogModule::LogLevel::INFO) << pthreadName << "等待任务"<<_IsSleepNum;_cond.Wait(_mutex);LOG(LogModule::LogLevel::INFO) << pthreadName << "有无任务";--_IsSleepNum;}if(!_Isrun && _tasks.empty()){LOG(LogModule::LogLevel::INFO) << pthreadName << " quit...";break;}t = _tasks.front();_tasks.pop();}t();//执行任务不需要加锁,并非临界资源}}bool QueueAddTask(const T& in){if(_Isrun){// LOG(LogModule::LogLevel::INFO)  << "添加任务";_tasks.push(in);if(_IsSleepNum == _tasks.size()){wakeUpOne();}return true;}return false;}static ThreadPool<T> *GetInstance(){if(inc == nullptr){// LOG(LogModule::LogLevel::INFO) << "inc == nullptr";LockGuard lock(mutex);if(inc == nullptr){inc = new ThreadPool<T>();inc->start();}}// LOG(LogModule::LogLevel::INFO) << "inc != nullptr";return inc;}private:std::vector<Thread> _threads;std::queue<T> _tasks;Mutex _mutex;Cond _cond;int _num; // 线程池总数int _IsSleepNum;bool _Isrun;static ThreadPool<T>* inc;static Mutex mutex;};template <typename T>ThreadPool<T>* ThreadPool<T>::inc = nullptr;template <typename T>Mutex ThreadPool<T>::mutex;
}

mian.cpp

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "Task.hpp"#include <random>
#include <mutex>thread_local std::mt19937 gen(std::random_device{}());void Result() {std::uniform_int_distribution<int> dist_x(0, 9);std::uniform_int_distribution<int> dist_y(0, 19);int x = dist_x(gen);int y = dist_y(gen);LogModule::LOG(LogModule::LogLevel::INFO) << x + y;// std::cout << x + y << std::endl;
}
// #define offsetof(type, member) ((size_t)&(((type *)0)->member))int main() 
{while(1){ThreadPoolModule::ThreadPool<task_t>::GetInstance()->QueueAddTask(Result);sleep(1);}ThreadPoolModule::ThreadPool<task_t>::GetInstance()->stop();return 0;
}

二. 单例模式

1. 什么是设计模式

设计模式是在软件开发中,针对特定问题场景总结出的可复用解决方案,是经过验证的、优化的代码设计经验。它描述了在特定场景下,如何组织类、对象、接口及其交互关系,以解决代码复用、扩展性、可读性等问题。

设计模式不直接提供具体代码,而是提供一种思想或模板,帮助开发者写出更健壮、灵活、易于维护的代码。常见的设计模式分类包括创建型(如单例、工厂)、结构型(如代理、装饰器)、行为型(如观察者、策略)等。

2. 什么是单例模式

单例模式是创建型设计模式的一种,其核心目标是:保证一个类在整个应用中只能实例化出一个对象,并提供一个全局唯一的访问点。

单例模式通常用于控制资源的统一管理(如线程池、数据库连接池、日志工具等),避免重复创建对象导致的资源浪费或状态不一致问题。

1. 饿汉模式(Eager Initialization)

核心思想:在类加载(静态成员初始化)时就创建唯一实例,依赖 C++ 全局 / 静态变量的初始化机制保证单例。

cpp

运行

#include <iostream>class SingletonHungry {
private:// 1. 私有静态成员:类加载时初始化唯一实例static SingletonHungry instance;// 2. 私有构造函数:禁止外部实例化SingletonHungry() {std::cout << "饿汉模式实例被创建" << std::endl;}// 禁用拷贝构造和赋值运算符(防止通过拷贝创建新实例)SingletonHungry(const SingletonHungry&) = delete;SingletonHungry& operator=(const SingletonHungry&) = delete;public:// 3. 公共静态方法:返回唯一实例static SingletonHungry& getInstance() {return instance;}void doSomething() {std::cout << "饿汉模式:执行任务" << std::endl;}
};// 类外初始化静态成员(C++要求),此时实例被创建
SingletonHungry SingletonHungry::instance;// 测试
int main() {SingletonHungry& s1 = SingletonHungry::getInstance();SingletonHungry& s2 = SingletonHungry::getInstance();// 验证是否为同一实例(地址相同)std::cout << "s1地址:" << &s1 << ", s2地址:" << &s2 << std::endl;s1.doSomething();return 0;
}

特点

  • 线程安全:C++ 中全局 / 静态变量的初始化在程序启动阶段(main 函数前)完成,由编译器保证线程安全(C++11 及以上)。
  • 缺点:如果实例占用资源大且程序全程未使用,会造成资源浪费。

2. 懒汉模式(Lazy Initialization,饱汉模式)

核心思想:延迟初始化,仅在第一次调用getInstance()时创建实例,需处理多线程安全问题。

(1)基础版(非线程安全)

cpp

运行

#include <iostream>class SingletonLazy {
private:// 1. 私有静态指针:暂不初始化static SingletonLazy* instance;// 2. 私有构造函数SingletonLazy() {std::cout << "懒汉模式实例被创建" << std::endl;}// 禁用拷贝和赋值SingletonLazy(const SingletonLazy&) = delete;SingletonLazy& operator=(const SingletonLazy&) = delete;public:// 3. 公共静态方法:首次调用时创建实例static SingletonLazy* getInstance() {if (instance == nullptr) { // 第一次调用时初始化instance = new SingletonLazy();}return instance;}void doSomething() {std::cout << "懒汉模式:执行任务" << std::endl;}// 可选:手动释放资源(单例通常不需要,程序结束时自动释放)static void destroyInstance() {if (instance != nullptr) {delete instance;instance = nullptr;}}
};// 类外初始化静态指针为nullptr
SingletonLazy* SingletonLazy::instance = nullptr;// 测试(单线程环境下正确,多线程可能创建多个实例)
int main() {SingletonLazy* s1 = SingletonLazy::getInstance();SingletonLazy* s2 = SingletonLazy::getInstance();std::cout << "s1地址:" << s1 << ", s2地址:" << s2 << std::endl;s1->doSomething();SingletonLazy::destroyInstance(); // 手动释放return 0;
}

问题:多线程环境下,多个线程可能同时进入if (instance == nullptr),导致创建多个实例(线程不安全)。

(2)线程安全版(C++11 及以上推荐)

利用 C++11 的局部静态变量初始化线程安全特性(标准规定:局部静态变量的初始化在多线程环境下是互斥的),实现简洁且安全的懒汉模式:

cpp

运行

#include <iostream>class SingletonLazySafe {
private:// 私有构造函数SingletonLazySafe() {std::cout << "线程安全懒汉模式实例被创建" << std::endl;}// 禁用拷贝和赋值SingletonLazySafe(const SingletonLazySafe&) = delete;SingletonLazySafe& operator=(const SingletonLazySafe&) = delete;public:// 关键:返回局部静态变量的引用,C++11保证初始化线程安全static SingletonLazySafe& getInstance() {static SingletonLazySafe instance; // 首次调用时初始化,仅一次return instance;}void doSomething() {std::cout << "线程安全懒汉模式:执行任务" << std::endl;}
};// 测试(多线程环境下安全)
int main() {SingletonLazySafe& s1 = SingletonLazySafe::getInstance();SingletonLazySafe& s2 = SingletonLazySafe::getInstance();std::cout << "s1地址:" << &s1 << ", s2地址:" << &s2 << std::endl;s1.doSomething();return 0;
}

特点

  • 线程安全:C++11 及以上标准保证局部静态变量的初始化是线程安全的,无需额外加锁。
  • 延迟加载:仅在首次调用getInstance()时初始化,节省资源。
  • 简洁高效:避免了指针管理和手动释放的问题,推荐使用。

总结

  • 饿汉模式:C++ 中通过全局静态成员初始化实现,线程安全但可能提前占用资源。
  • 懒汉模式:推荐使用 C++11 的局部静态变量方式,兼顾线程安全和延迟加载,是最简洁的实现。
http://www.dtcms.com/a/574367.html

相关文章:

  • 建站全过程品牌网站建站
  • Linux之rsyslog(3)模板配置
  • 做网站只用前端知识可以吗热点新闻
  • 免费夸克网盘不限速下载简单方法
  • 本地部署网站流量分析工具 Matomo 并实现外部访问
  • 光伏企业的智能仓储管理如何变革
  • 上海一家做服饰包鞋穿上用品的网站网站中文域名到期有没有影响
  • milvus向量化数据库部署
  • 接口测试知识总结
  • 什么是惊群效应
  • 装饰网站的业务员都是怎么做的做网站包括图片设计吗
  • 网站设计要点wordpress 屏蔽插件更新
  • 企业网站模板源代码下载wordpress 教程网
  • 番禺做网站开发产品外观设计图片
  • 从零开始搭建 flask 博客实验(5)
  • 时序数据库系列(三):InfluxDB数据写入Line Protocol详解
  • 网站个人和公司有什么区别是什么一些做义工的旅游网站
  • 学校门户网站建设的优势网站竞价推广哪个好
  • 公司网站策划宣传seo入门课程
  • 一种用于乳腺癌风险预测的新型时间衰减影像组学整合网络(TRINet)|文献速递-文献分享
  • 使用css 给div添加四角线框
  • 伪原创嵌入网站用腾讯云做淘宝客网站视频
  • 医院建设官方网站必要性郑州网站建设企业推荐
  • 基于LQR和PID控制算法的一级倒立摆MATLAB实现
  • 临沂企业网站十大免费行情软件网站下载
  • 从Java后端到Python大模型:我的学习转型与规划
  • 电商创客网站建设方案高端网站案例
  • 苏州做公司网站艺术字体转换器
  • 手机网站建设请示浙江立鹏建设有限公司网站
  • 延吉网站网站建设wordpress 树状目录结构