news 2026/3/26 17:01:32

简单线程池实现(单例模式)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
简单线程池实现(单例模式)

1.概念

基本概念

线程池是一种多线程处理形式,它预先创建一组线程并管理它们,避免频繁创建和销毁线程带来的性能开销。

在 Linux 环境下,线程池(Thread Pool)是一种常用的并发编程模型,用于复用线程资源,避免频繁创建和销毁线程带来的性能开销。它特别适用于高并发、短任务的场景,比如 Web 服务器、数据库连接池、异步任务处理等

为什么需要线程池

  • 降低资源消耗:重复利用已创建的线程
  • 提高响应速度:任务到达时可直接执行,无需等待线程创建
  • 提高线程可管理性:统一分配、调优和监控
  • 防止资源耗尽:避免无限制创建线程导致系统崩溃

✅ 线程池的核心组件

组件作用说明
任务队列存放待执行的任务(函数指针、lambda、function 等)
工作线程从任务队列中取出任务并执行
同步机制使用互斥锁和条件变量实现线程间通信
线程复用线程执行完任务后不退出,而是继续等待下一个任务

✅ 线程池的优势

优势说明
性能提升避免频繁创建/销毁线程
资源控制限制最大并发线程数,防止系统过载
任务调度可配合优先级队列、延迟任务等机制

✅ 线程池的注意事项

问题解决方案
任务阻塞避免在线程池中执行阻塞 IO,必要时使用异步 IO
异常处理任务中抛出的异常不会自动传播,需手动捕获

线程池作用

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

线程池的应用场景

• 需要大量的线程来完成任务,且完成任务的时间比较短。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

• 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

• 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

线程池的种类

a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中的任务接口

b. 浮动线程池,其他同上

此处,我们选择固定线程个数的线程池。

2.实现

前置头文件

thread_pool_test:thread_pool_test.pp g++-o $@ $^-std=c++17-lpthread.PHONY:clean clean:rm-f thread_pool_test
#pragmaonce#include<iostream>#include<pthread.h>#include"Mutex.hpp"classCond{public:Cond(){pthread_cond_init(&_cond,nullptr);}voidWait(Mutex&lock){intn=pthread_cond_wait(&_cond,lock.Get());}voidNotifyOne(){intn=pthread_cond_signal(&_cond);(void)n;}voidNotifyAll(){intn=pthread_cond_broadcast(&_cond);(void)n;}~Cond(){pthread_cond_destroy(&_cond);}private:pthread_cond_t _cond;};
#pragmaonce#include<iostream>#include<mutex>#include<pthread.h>classMutex{public:Mutex(){pthread_mutex_init(&_lock,nullptr);}voidLock(){pthread_mutex_lock(&_lock);}voidUnlock(){pthread_mutex_unlock(&_lock);}pthread_mutex_t*Get(){return&_lock;}~Mutex(){pthread_mutex_destroy(&_lock);}private:pthread_mutex_t _lock;};classLockGuard{public:LockGuard(Mutex*_mutex):_mutexp(_mutex){_mutexp->Lock();}~LockGuard(){_mutexp->Unlock();}private:Mutex*_mutexp;};
#pragmaonce#include<iostream>#include<string>#include<filesystem>// C++17 文件操作#include<fstream>#include<ctime>#include<unistd.h>#include<memory>#include<sstream>#include"Mutex.hpp"// 规定出场景的日志等级enumclassLogLevel{DEBUG,INFO,WARNING,ERROR,FATAL};// 日志转换成为字符串std::stringLevel2String(LogLevel level){switch(level){caseLogLevel::DEBUG:return"Debug";caseLogLevel::INFO:return"Info";caseLogLevel::WARNING:return"Warning";caseLogLevel::ERROR:return"Error";caseLogLevel::FATAL:return"Fatal";default:return"Unknown";}}// 根据时间戳,获取可读性较强的时间信息// 20XX-08-04 12:27:03std::stringGetCurrentTime(){// 1. 获取时间戳time_t currtime=time(nullptr);// 2. 如何把时间戳转换成为20XX-08-04 12:27:03structtmcurrtm;localtime_r(&currtime,&currtm);// 3. 转换成为字符串chartimebuffer[64];snprintf(timebuffer,sizeof(timebuffer),"%4d-%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);returntimebuffer;}// 策略模式,策略接口// 1. 刷新的问题 -- 假设我们已经有了一条完整的日志,string->设备(显示器,文件)// 基类方法classLogStrategy{public:// 不同模式核心是刷新方式的不同virtual~LogStrategy()=default;virtualvoidSyncLog(conststd::string&logmessage)=0;};// 控制台日志策略,就是日志只向显示器打印,方便我们debug// 显示器刷新classConsoleLogStrategy:publicLogStrategy{public:~ConsoleLogStrategy(){}voidSyncLog(conststd::string&logmessage)override{{LockGuardlockguard(&_lock);std::cout<<logmessage<<std::endl;}}private:// 显示器也是临界资源,保证输出线程安全Mutex _lock;};// 默认路径和日志名称conststd::string logdefaultdir="log";conststaticstd::string logfilename="test.log";// 文件日志策略// 文件刷新classFileLogStrategy:publicLogStrategy{public:// 构造函数,建立出来指定的目录结构和文件结构FileLogStrategy(conststd::string&dir=logdefaultdir,conststd::string filename=logfilename):_dir_path_name(dir),_filename(filename){LockGuardlockguard(&_lock);if(std::filesystem::exists(_dir_path_name)){return;}try{std::filesystem::create_directories(_dir_path_name);}catch(conststd::filesystem::filesystem_error&e){std::cerr<<e.what()<<"\r\n";}}// 将一条日志信息写入到文件中voidSyncLog(conststd::string&logmessage)override{{LockGuardlockguard(&_lock);std::string target=_dir_path_name;target+="/";target+=_filename;// 追加方式std::ofstreamout(target.c_str(),std::ios::app);// appendif(!out.is_open()){return;}out<<logmessage<<"\n";// out.writeout.close();}}~FileLogStrategy(){}private:std::string _dir_path_name;// logstd::string _filename;// hello.log => log/hello.logMutex _lock;};// 具体的日志类// 1. 定制刷新策略// 2. 构建完整的日志classLogger{public:Logger(){}voidEnableConsoleLogStrategy(){_strategy=std::make_unique<ConsoleLogStrategy>();}voidEnableFileLogStrategy(){_strategy=std::make_unique<FileLogStrategy>();}// 内部类,实现RAII风格的日志格式化和刷新// 这个LogMessage,表示一条完整的日志对象classLogMessage{public:// RAII风格,构造的时候构建好日志头部信息LogMessage(LogLevel level,std::string&filename,intline,Logger&logger):_curr_time(GetCurrentTime()),_level(level),_pid(getpid()),_filename(filename),_line(line),_logger(logger){// stringstream不允许拷贝,所以这里就当做格式化功能使用std::stringstream ss;ss<<"["<<_curr_time<<"] "<<"["<<Level2String(_level)<<"] "<<"["<<_pid<<"] "<<"["<<_filename<<"] "<<"["<<_line<<"]"<<" - ";_loginfo=ss.str();}// 重载 << 支持C++风格的日志输入,使用模版,表示支持任意类型template<typenameT>LogMessage&operator<<(constT&info){std::stringstream ss;ss<<info;_loginfo+=ss.str();return*this;}// RAII风格,析构的时候进行日志持久化,采用指定的策略~LogMessage(){if(_logger._strategy){_logger._strategy->SyncLog(_loginfo);}}private:std::string _curr_time;// 日志时间LogLevel _level;// 日志等级pid_t _pid;// 进程pidstd::string _filename;int_line;std::string _loginfo;// 一条合并完成的,完整的日志信息Logger&_logger;// 引用外部logger类, 方便使用策略进行刷新};// 故意拷贝,形成LogMessage临时对象,后续在被<<时,会被持续引用,// 直到完成输入,才会自动析构临时LogMessage,至此也完成了日志的显示或者刷新// 同时,形成的临时对象内包含独立日志数据// 未来采用宏替换,进行文件名和代码行数的获取LogMessageoperator()(LogLevel level,std::string filename,intline){returnLogMessage(level,filename,line,*this);}~Logger(){}private:// 写入日志的策略std::unique_ptr<LogStrategy>_strategy;};// 定义全局的logger对象Logger logger;// 使用宏,可以进行代码插入,方便随时获取文件名和行号#defineLOG(level)logger(level,__FILE__,__LINE__)// 提供选择使用何种日志策略的方法#defineEnableConsoleLogStrategy()logger.EnableConsoleLogStrategy()#defineEnableFileLogStrategy()logger.EnableFileLogStrategy()
#pragmaonce#include<iostream>#include<functional>#include<unistd.h>#include<sstream>classTask{public:Task(){}Task(intx,inty):a(x),b(y){}voidExecute(){result=a+b;}voidoperator()(){// sleep(1);Execute();}voidPrint(){std::cout<<a<<" + "<<b<<" = "<<result<<std::endl;}std::stringResult2String(){std::stringstream ss;ss<<a<<" + "<<b<<" = "<<result;returnss.str();}private:inta;intb;intresult;};
#pragmaonce#include<iostream>#include<string>#include<pthread.h>#include<unistd.h>#include<functional>#include<sys/syscall.h>/* For SYS_xxx definitions */#include"Logger.hpp"#defineget_lwp_id()syscall(SYS_gettid)usingfunc_t=std::function<void(conststd::string&name)>;conststd::string threadnamedefault="None-Name";classThread{public:Thread(func_t func,conststd::string&name=threadnamedefault):_name(name),_func(func),_isrunning(false){LOG(LogLevel::INFO)<<_name<<" create thread obj success";}staticvoid*start_routine(void*args){Thread*self=static_cast<Thread*>(args);self->_isrunning=true;self->_lwpid=get_lwp_id();self->_func(self->_name);pthread_exit((void*)0);}voidStart(){intn=pthread_create(&_tid,nullptr,start_routine,this);if(n==0){LOG(LogLevel::INFO)<<_name<<" running success";}}voidStop(){intn=pthread_cancel(_tid);// 太简单粗暴了(void)n;}// void Die()// {// pthread_cancel(_tid);// }// 检测线程结束并且回收的功能voidJoin(){if(!_isrunning)return;intn=pthread_join(_tid,nullptr);if(n==0){LOG(LogLevel::INFO)<<_name<<" pthread_join success";}}~Thread(){// LOG(LogLevel::INFO) << _name << " destory thread obj success";}private:bool_isrunning;pthread_t _tid;pid_t _lwpid;std::string _name;func_t _func;};

thread_pool

#pragmaonce#include<queue>#include<vector>#include"Thread.hpp"#include"Cond.hpp"#include"Logger.hpp"usingnamespacestd;conststaticintdefault_thread_num=5;template<typenameT>classThreadPool{voidRoutine(string name){while(true){T t;{LockGuardlock(&_mutex);// 如果线程池正在运行且任务队列为空// 注意,一定要使用while,防止出错while(_is_running&&QueueIsEmpty()){_wait_thread_num++;_cond.Wait(_mutex);// 线程唤醒后执行的是这里的逻辑 如果队列为空了 就要离开循环了_wait_thread_num--;}// 如果线程池要退出且任务队列为空if(!_is_running&&QueueIsEmpty()){LOG(LogLevel::INFO)<<"线程池准备退出&&任务队列为空 "<<name.c_str()<<"退出";break;}// 此时任务队列一定不为空,存在两种情况// 1. 线程池准备退出 -- 消耗历史任务// 2. 线程池没有准备退出 -- 正常工作t=_task_queue.front();_task_queue.pop();// LOG(LogLevel::DEBUG) << name << "::::" <<_task_queue.size();// if (!QueueIsEmpty())// {// t = _task_queue.front();// //Linux上面实现的stl库,队列为空的时候(size==0)还可以去数据导致(size--)// //拿到了一个0+0 = 0的任务// //然后size(size_t类型的数据没有负数)就变成了一个非常大的正数,队列就不为空了// //这里段错误和数组的越界访问类似 然后一直拿去数据 触发了段错误// _task_queue.pop();// }}// 此时,线程已经把任务从临界资源获取到线程私有!临界区 -> 线程私有的栈// 处理任务时不需要再临界区内部进行,并发进行效率更高t();// 规定,未来的任务必须这样处理!operate()重载LOG(LogLevel::DEBUG)<<name<<" handler task: "<<t.Result2String();}}public:ThreadPool(intthreadnum=default_thread_num):_thread_num(threadnum),_wait_thread_num(0),_is_running(false){for(inti=1;i<=_thread_num;i++){// 方法1:// auto f = std::bind(hello, this);// 方法2string name="thread-"+to_string(i);// emplace_back()是STL容器(如vector、deque、list)的成员函数,// 用于在容器尾部直接构造元素,避免不必要的拷贝或移动。_threads.emplace_back([this](conststring&name){this->Routine(name);},name);}LOG(LogLevel::INFO)<<"thread pool obj create success";}voidStart(){if(_is_running)return;_is_running=true;for(auto&t:_threads)t.Start();}voidStop(){// 线程池要退出,不是立刻就能退出的,它要把任务队列中的任务处理完后才能退出if(!_is_running)return;_is_running=false;// 线程池都要退出了,那些休眠的线程还休眠什么,赶紧把他们全部唤醒// 处理完任务后线程池好退出if(_wait_thread_num)_cond.NotifyAll();}voidEnqueue(constT&task){// 如果线程池准备退出,任务就不要入队列了if(!_is_running)return;{LockGuardlock(&_mutex);_task_queue.push(task);// LOG(LogLevel::DEBUG) << "一个任务入队列了";if(_wait_thread_num)_cond.NotifyOne();}}voidWait(){for(auto&t:_threads){t.Join();}LOG(LogLevel::INFO)<<"thread pool wait success";}boolQueueIsEmpty(){return_task_queue.empty();}~ThreadPool(){}private:// 任务队列queue<T>_task_queue;// 整体使用的临界资源vector<Thread>_threads;int_thread_num;// 线程池中线程个数int_wait_thread_num;// 线程池正在等待的线程个数// 保护线程池安全Mutex _mutex;Cond _cond;// 检测线程池是否在运行bool_is_running;};
#include"ThreadPool.hpp"#include"Task.hpp"#include<time.h>#include<memory>intmain(){srand(time(nullptr));//使用控制台策略,向显示器输出日志EnableConsoleLogStrategy();unique_ptr<ThreadPool<Task>>tp=make_unique<ThreadPool<Task>>();tp->Start();intcnt=10;while(cnt--){intx=rand()%9+1;inty=rand()%9+1;Tasktask(x,y);tp->Enqueue(task);sleep(1);}tp->Stop();tp->Wait();return0;}

注意若是if{…}在while{…}前面就会出错

如下:

3 线程池单例模式

3.1 概念

单例模式确保一个类只有一个实例,并提供一个全局访问点。

线程池使用单例模式的理由:

  1. 系统中通常只需要一个全局的线程池
  2. 避免资源浪费(多个线程池竞争系统资源)
  3. 便于统一管理和监控

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

[洗碗的例子]

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度

核心区别:

特性饿汉式 (Eager)懒汉式 (Lazy)
初始化时机程序启动时/类加载时第一次使用时
线程安全天然线程安全需要额外同步机制
资源占用启动时即占用资源使用时才占用资源
性能启动慢,运行时快启动快,第一次使用稍慢
实现复杂度简单较复杂

3.2.1 饿汉实现方式

核心思想

“提前准备,立即加载”- 在程序启动或类加载时就创建实例。

template<typenameT>classSingleton{staticT data;//静态全局变量定义在全局数据区,编译后就会为它分配空间public:staticT*GetInstance(){return&data;}};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.

饿汉式特点分析

优点:

  1. 线程安全:无需考虑多线程同步问题
  2. 性能好:获取实例时直接返回,无锁开销
  3. 实现简单:代码简洁,不易出错

缺点:

  1. 启动时间:可能增加程序启动时间
  2. 资源浪费:即使不使用也占用资源
  3. 初始化顺序:不同编译单元间的静态变量初始化顺序不确定

适用场景:

  • 单例初始化开销小
  • 单例在程序运行中必定会被使用
  • 对性能要求极高,不能忍受锁开销
  • 多线程环境且不想处理同步问题

3.2.2 懒汉实现方式

核心思想

“用时创建,延迟加载”- 在第一次请求时才创建实例。

template<typenameT>classSingleton{staticT*inst;public:staticT*GetInstance(){if(inst==NULL){inst=newT();}returninst;}};

存在一个严重的问题, 线程不安全.

第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.

但是后续再次调用, 就没有问题了

懒汉式特点分析

优点:

  1. 延迟加载:只有使用时才创建,节省资源
  2. 启动快速:不增加程序启动时间
  3. 灵活性高:可以根据需要动态创建

缺点:

  1. 线程安全复杂:需要处理多线程同步
  2. 首次访问慢:第一次获取实例需要创建和同步
  3. 实现复杂:需要考虑各种边界情况

适用场景:

  • 单例初始化开销大
  • 单例可能不会被使用
  • 系统资源有限,需要按需加载
  • 对启动时间敏感的应用

3.2.3 深度对比与分析

内存模型分析
// 饿汉式内存布局// 编译时确定,在数据段分配内存// 程序加载时即初始化// 懒汉式内存布局// 运行时确定,在堆上分配内存// 第一次访问时初始化
线程安全性对比
实现方式饿汉式懒汉式
基础实现线程安全线程不安全
加锁实现不需要需要锁机制
双重检查不需要需要DCLP
现代实现总是安全call_once或局部静态变量
选择指南

选择饿汉式的情况:

  1. 单例的创建和运行时开销非常小
  2. 程序运行过程中一定会使用该单例
  3. 希望避免任何锁开销
  4. 单例的初始化不依赖其他未初始化的资源

选择懒汉式的情况:

  1. 单例初始化成本高(连接数据库、读取大文件等)
  2. 单例可能在整个程序生命周期中都不被使用
  3. 单例的初始化依赖运行时信息或其他单例
  4. 需要动态控制单例的创建时机

3.2.4 总结

特性饿汉式懒汉式
哲学积极准备按需分配
实现简单直接复杂但灵活
线程安全天然安全需额外保障
资源使用启动时占用使用时占用
性能特点启动慢使用快启动快第一次慢
现代推荐静态成员或局部静态变量局部静态变量(call_once)

最佳实践建议:

  1. 优先使用基于局部静态变量的懒汉式(C++11及以上)
  2. 如果确定单例一定被使用且初始化快,可以考虑饿汉式
  3. 在多线程环境中,避免手写双重检查锁定,使用标准库提供的线程安全机制
  4. 考虑是否真的需要单例,评估是否有更好的设计模式替代

3.3 懒汉方式实现单例模式

其他的头文件都和上面一样

ThreadPool:ThreadPool.cpp g++-o $@ $^-std=c++17-lpthread.PHONY:clean clean:rm-f ThreadPool
#pragmaonce#include<queue>#include<vector>#include"Thread.hpp"#include"Cond.hpp"#include"Logger.hpp"usingnamespacestd;// 单例线程池 - 懒汉模式conststaticintdefault_thread_num=3;template<typenameT>classThreadPool{voidRoutine(string name){while(true){T t;{LockGuardlock(&_mutex);// 如果线程池正在运行且任务队列为空// 注意,一定要使用while,防止出错while(_is_running&&QueueIsEmpty()){_wait_thread_num++;_cond.Wait(_mutex);// 线程唤醒后执行的是这里的逻辑 如果队列为空了 就要离开循环了_wait_thread_num--;}// 如果线程池要退出且任务队列为空if(!_is_running&&QueueIsEmpty()){LOG(LogLevel::INFO)<<"线程池准备退出&&任务队列为空 "<<name.c_str()<<"退出";break;}// 此时任务队列一定不为空,存在两种情况// 1. 线程池准备退出 -- 消耗历史任务// 2. 线程池没有准备退出 -- 正常工作t=_task_queue.front();_task_queue.pop();// LOG(LogLevel::DEBUG) << name << "::::" <<_task_queue.size();// if (!QueueIsEmpty())// {// t = _task_queue.front();// //Linux上面实现的stl库,队列为空的时候(size==0)还可以去数据导致(size--)// //拿到了一个0+0 = 0的任务// //然后size(size_t类型的数据没有负数)就变成了一个非常大的正数,队列就不为空了// //这里段错误和数组的越界访问类似 然后一直拿去数据 触发了段错误// _task_queue.pop();// }}// 此时,线程已经把任务从临界资源获取到线程私有!临界区 -> 线程私有的栈// 处理任务时不需要再临界区内部进行,并发进行效率更高t();// 规定,未来的任务必须这样处理!operate()重载LOG(LogLevel::DEBUG)<<name<<" handler task: "<<t.Result2String();}}private:// 将构造函数设为私有,不允许用户直接创建对象ThreadPool(intthreadnum=default_thread_num):_thread_num(threadnum),_wait_thread_num(0),_is_running(false){for(inti=1;i<=_thread_num;i++){// 方法1:// auto f = std::bind(hello, this);// 方法2string name="thread-"+to_string(i);// emplace_back()是STL容器(如vector、deque、list)的成员函数,// 用于在容器尾部直接构造元素,避免不必要的拷贝或移动。_threads.emplace_back([this](conststring&name){this->Routine(name);},name);}LOG(LogLevel::INFO)<<"thread pool obj create success";}// 禁掉拷贝构造和赋值重载ThreadPool<T>&operator=(constThreadPool<T>&)=delete;ThreadPool(constThreadPool<T>&)=delete;public:voidStart(){if(_is_running)return;_is_running=true;for(auto&t:_threads)t.Start();}voidStop(){// 线程池要退出,不是立刻就能退出的,它要把任务队列中的任务处理完后才能退出if(!_is_running)return;_is_running=false;// 线程池都要退出了,那些休眠的线程还休眠什么,赶紧把他们全部唤醒// 处理完任务后线程池好退出if(_wait_thread_num)_cond.NotifyAll();}voidEnqueue(constT&task){// 如果线程池准备退出,任务就不要入队列了if(!_is_running)return;{LockGuardlock(&_mutex);_task_queue.push(task);// LOG(LogLevel::DEBUG) << "一个任务入队列了";if(_wait_thread_num)_cond.NotifyOne();}}voidWait(){for(auto&t:_threads){t.Join();}LOG(LogLevel::INFO)<<"thread pool wait success";}boolQueueIsEmpty(){return_task_queue.empty();}// 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针// 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针// 静态成员函数只能访问静态成员,非静态的成员函数,可以访问任意的静态成员变量和静态成员函数// 地址转字符串,证明多线程申请的单例都是同一个staticstd::stringToHex(ThreadPool<T>*addr){charbuffer[64];snprintf(buffer,sizeof(buffer),"%p",addr);returnbuffer;}// 获取单例staticThreadPool<T>*GetInstance(){// A, B, c// 线程安全,提高效率式的获取单例// 双重 if 判定, 避免不必要的锁竞争if(!_instance){// 外层if是为了防止获取单例后多线程申请单例时判断前还要申请锁,降低效率// 保证第二次之后,所有线程,不用在加锁,直接返回_instance单例对象LockGuardlockguard(&_singleton_lock);if(!_instance){_instance=newThreadPool<T>();LOG(LogLevel::DEBUG)<<"线程池单例首次被使用,创建并初始化, addr: "<<ToHex(_instance);_instance->Start();}}else{LOG(LogLevel::DEBUG)<<"线程池单例已经存在,直接获取, addr: "<<ToHex(_instance);}return_instance;}~ThreadPool(){}private:// 任务队列queue<T>_task_queue;// 整体使用的临界资源vector<Thread>_threads;int_thread_num;// 线程池中线程个数int_wait_thread_num;// 线程池正在等待的线程个数// 保护线程池安全Mutex _mutex;Cond _cond;// 检测线程池是否在运行bool_is_running;// 单例中静态指针// 需要设置 volatile 关键字, 防止被编译器优化.// volatile static ThreadPool<T> *_instance;// 用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化。// 类内定义,类外初始化// 添加单例模式staticThreadPool<T>*_instance;staticMutex _singleton_lock;};template<classT>ThreadPool<T>*ThreadPool<T>::_instance=nullptr;template<classT>Mutex ThreadPool<T>::_singleton_lock;
#include"Task.hpp"#include"ThreadPool.hpp"#include<memory>#include<time.h>intmain(){srand(time(nullptr)^getpid());EnableConsoleLogStrategy();//编译时会报错,单例模式不允许用户直接创建线程池对象// std::unique_ptr<ThreadPool<Task>> tp = std::make_unique<ThreadPool<Task>>(10);// tp->Start();intcnt=10;while(cnt--){// 生产任务intx=rand()%10+1;usleep(rand()%73);inty=rand()%5+1;Taskt(x,y);// push到线程池中,处理// 突破类域就可以访问静态成员,可以通过 类名::静态成员(不需要对象就能调用)// 或者 对象.静态成员 来访问静态成员变量和静态成员函数ThreadPool<Task>::GetInstance()->Enqueue(t);sleep(1);}ThreadPool<Task>::GetInstance()->Stop();ThreadPool<Task>::GetInstance()->Wait();return0;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/26 14:48:02

前端也能玩转AI音效?HTML+JavaScript调用HunyuanVideo-Foley实践

前端也能玩转AI音效&#xff1f;HTMLJavaScript调用HunyuanVideo-Foley实践 在短视频内容爆炸式增长的今天&#xff0c;一个15秒的视频是否能留住用户&#xff0c;往往不只取决于画面——声音正在成为决定体验上限的关键因子。脚步踩在碎石路上的沙沙声、玻璃杯滑落桌面的清脆撞…

作者头像 李华
网站建设 2026/3/25 11:59:37

diskinfo检测RAID阵列性能匹配Qwen3-VL-30B读取需求

diskinfo检测RAID阵列性能匹配Qwen3-VL-30B读取需求 在部署像 Qwen3-VL-30B 这类超大规模视觉语言模型时&#xff0c;一个常被低估的瓶颈正悄然浮现&#xff1a;存储I/O能力跟不上计算吞吐。即便配备了顶级GPU集群&#xff0c;若模型权重加载缓慢、推理过程频繁等待数据&#x…

作者头像 李华
网站建设 2026/3/23 13:13:17

告别Gradle版本困扰:AI工具效率提升10倍的秘密

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 构建一个Gradle版本问题效率对比工具&#xff0c;左侧展示传统解决流程&#xff1a;手动检查gradle-wrapper.properties、查阅兼容性文档、尝试不同版本、处理依赖冲突等耗时步骤&a…

作者头像 李华
网站建设 2026/3/25 6:56:21

66666

666666

作者头像 李华
网站建设 2026/3/22 19:39:24

88888

88888

作者头像 李华
网站建设 2026/3/23 18:52:33

使用Ollama运行GPT-OSS-20B实现低延迟对话响应的调优技巧

使用Ollama运行GPT-OSS-20B实现低延迟对话响应的调优技巧 你有没有遇到过这样的场景&#xff1a;在使用云端大模型时&#xff0c;明明问题已经输入完毕&#xff0c;却要等上一两秒才能看到第一个字蹦出来&#xff1f;更别提网络波动导致的超时、敏感数据不敢上传的顾虑&#xf…

作者头像 李华