
◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Linux
⭐️此方的GitHub: github_此方
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
- 概要&序論
- 一、线程池的简单介绍
-
- [1.1 线程池的基本概念与核心价值](#1.1 线程池的基本概念与核心价值)
- [1.2 线程池的经典应用场景](#1.2 线程池的经典应用场景)
-
- [1.2.1 高并发与短任务场景](#1.2.1 高并发与短任务场景)
- [1.2.2 敏捷响应与性能苛刻型应用](#1.2.2 敏捷响应与性能苛刻型应用)
- [1.2.3 突发性流量防护](#1.2.3 突发性流量防护)
- [1.3 线程池的主要种类与选型](#1.3 线程池的主要种类与选型)
- 二、线程安全的单例模式详解
-
- [2.1 单例模式的概念与生活趣喻](#2.1 单例模式的概念与生活趣喻)
- [2.2 单例模式的代码实现与演进](#2.2 单例模式的代码实现与演进)
-
- [2.2.1 饿汉方式的实现](#2.2.1 饿汉方式的实现)
- [2.2.2 懒汉方式的基础实现与隐患](#2.2.2 懒汉方式的基础实现与隐患)
- [2.2.3 线程安全的懒汉方式实现](#2.2.3 线程安全的懒汉方式实现)
- 三、基于单例模式的线程池完整源码
概要&序論
Hello大家好我是此方。本文系统介绍了线程池的核心概念、应用场景与选型分类,深入剖析了饿汉式及线程安全懒汉式单例模式的实现原理,并结合单例模式给出了线程池的完整源码实现。
一、线程池的简单介绍
1.1 线程池的基本概念与核心价值
线程池本质上是一种线程使用模式 。在多线程编程中,如果无节制地创建线程,线程数量过多会带来巨大的线程上下文切换与调度开销,进而显著破坏 CPU 的缓存局部性并降低系统的整体性能。
线程池通过在内部预先维护多个线程,使其处于等待状态,时刻准备接收并执行由监督管理者分配的并发任务。这种机制带来了多方面的核心收益:
- 极低的资源消耗与开销:避免了在处理短时间任务时频繁创建与销毁线程的巨大开销。
- 高效利用资源与防过载:既能保证系统内核与硬件资源的充分利用,又能有效防止线程过分调度。
系统的可用线程数量并非任意设定,它通常取决于硬件与系统资源的瓶颈,主要受限于可用的并发处理器数量、处理器内核数、内存容量以及网络 sockets 句柄数量等因素。
1.2 线程池的经典应用场景
结合线程池的特性,它在现代服务端与高并发系统中有着广泛的应用场景:
1.2.1 高并发与短任务场景
在任务数量巨大但单个任务执行时间较短的场景中,使用线程池非常合适。例如热门 WEB 服务器响应大量用户的网页点击请求。
相比之下,对于长时间运行的任务(如 Telnet 连接请求),由于 Telnet 会话持续的时间远大于线程自身的创建开销,线程池带来的性能提升就不再明显。
1.2.2 敏捷响应与性能苛刻型应用
对于对性能要求极为苛刻、必须迅速响应客户端请求的系统(如实时交易系统或高频 RPC 服务),线程池通过预先创建好工作线程,实现了任务到达时的"零等待"响应。
1.2.3 突发性流量防护
系统常会接收到突发性的大量请求。在没有线程池的情况下,系统会瞬间尝试创建大量线程。虽然理论上大部分操作系统支持的线程上限很高,但短时间内爆发式创建线程极易使内存达到极限并引发严重错误。线程池可以在承受突发流量的同时,将线程总量控制在安全范围内。
1.3 线程池的主要种类与选型
在实际工程落地中,常见的线程池模式主要分为以下两类:
- 固定数量线程池:预先创建固定数量的工作线程,这些线程循环从任务队列中获取任务对象,获取成功后直接执行任务对象中的接口方法。
- 浮动线程池:根据当前系统的实际负载动态增减线程数量,核心调度逻辑与固定线程池基本相同。
在本系列文章的设计与实现中,我们选择构建固定线程个数的线程池。

二、线程安全的单例模式详解
2.1 单例模式的概念与生活趣喻
某些类在整个系统中只应该具有一个对象(实例),这种设计模式就称为单例模式。就像现实生活中,一个男人只能有一个媳妇一样,具有天然的唯一性。
在很多服务器开发场景中,程序往往需要加载海量的数据(例如上百 G 的配置或缓存数据 )到内存中。此时如果重复创建对象,不仅会挤爆内存,还会导致数据不一致。因此,非常适合使用单例模式的类来统一管理这些数据。
单例模式有两种常见的类型------饿汉方式 与懒汉方式 。
为了直观理解单例模式的两种常见类型,我们可以用生活中的"洗碗"来做一个生动的对比:
- 饿汉方式:刚吃完饭,就立刻把碗洗干净。这样做的好处是,下一顿饭要吃的时候,随时能拿起干净的碗直接用餐(加载迅速)。
- 懒汉方式:吃完饭后先把碗放下不洗;直到下一顿饭真正用到这个碗的时候,才临时去洗碗。
懒汉方式最核心的思想就是延迟加载。在服务端启动阶段,如果不需要立即使用资源,就不去进行初始化,从而能够大幅优化服务器的启动速度。
2.2 单例模式的代码实现与演进
只要通过 Singleton 这个包装类来使用目标 T 对象,就能保证在一个进程中只存在一个 T 对象的实例。
2.2.1 饿汉方式的实现
饿汉方式在类加载或程序启动时就将静态实例初始化完毕:
cpp
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};
2.2.2 懒汉方式的基础实现与隐患
基础的懒汉模式将实例的创建延迟到第一次调用 GetInstance() 时:
cpp
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};
存在的问题 :上述简单实现存在严重的多线程安全问题。当多个线程在同一时间首次调用 GetInstance() 时,它们可能同时判断 inst == NULL 为真,进而创建出多份 T 对象的实例,破坏了单例约束。(不过,一旦实例创建完毕,后续的并发调用则不会引发问题。)
2.2.3 线程安全的懒汉方式实现
为了在多线程环境下安全地使用懒汉模式,我们需要加入互斥锁以及"双重检查锁"(Double-Checked Locking)机制:
cpp
// 懒汉模式,线程安全
template <typename T>
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字,防止指令重排与编译器优化
static std::mutex lock;
public:
static T* GetInstance() {
// 双重判定空指针,降低锁冲突的概率,提高性能
if (inst == NULL) {
lock.lock();
// 使用互斥锁,保证多线程情况下也只调用一次 new
if (inst == NULL) {
inst = new T();
}
lock.unlock();
}
return inst;
}
};
在该实现中,外层判断 用于避免实例创建后每次获取都加锁,大大降低了锁冲突概率;内层加锁与判断 则保证了首次并发调用时,有且仅有一个线程能够成功执行 new 操作。配合 volatile 关键字,能防止编译器过度优化及指令重排导致的未初始化指针暴露问题。
三、基于单例模式的线程池完整源码
3.1基本组件
3.1.1Cond.hpp
cpp
#ifndef __CONDMODULE__
#define __CONDMODULE__
#include <pthread.h>
#include "Mutex.hpp"
namespace ConModule
{
class Cond
{
public:
Cond()
{
int n = pthread_cond_init(&_cond, nullptr);
(void)n;
}
void Wait(MutexModule::Mutex &mutex)
{
int n = pthread_cond_wait(&_cond, mutex.Get());
(void)n;
}
void Signal()
{
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
int n = pthread_cond_destroy(&_cond);
(void)n;
}
private:
pthread_cond_t _cond;
};
}
#endif
3.1.2Mutex.hpp
cpp
#ifndef __MUTEXMODULE__
#define __MUTEXMODULE__
#include <pthread.h>
#include <filesystem>
#include <iostream>
#include <ctime>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <memory>
#include <cstdio>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
int n = pthread_mutex_init(&_mutex, nullptr);
(void)n;
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void UnLock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
int n = pthread_mutex_destroy(&_mutex);
(void)n;
}
pthread_mutex_t* Get()
{
return &_mutex;
}
private:
pthread_mutex_t _mutex;
};
class MutexGuard
{
public:
MutexGuard(Mutex& mutex)
: _mutex(mutex)
{
_mutex.Lock();
}
~MutexGuard()
{
_mutex.UnLock();
}
private:
Mutex& _mutex;
};
}
#endif
3.1.3Log.hpp
cpp
#ifndef __LOG_MODULE__
#define __LOG_MODULE__
#include "Mutex.hpp"
namespace LogModule
{
const std::string CAGE = "\n";
class LogFlashStrategy
{
public:
~LogFlashStrategy() = default ;
virtual void SyncLog(const std::string& message) = 0;
};
class ConsoleLogStrategy : public LogFlashStrategy
{
public:
void SyncLog(const std::string& logmessage) override
{
MutexModule::MutexGuard _MutexGuard (_mutex);
std::cout<<logmessage<<CAGE ;
}
private:
MutexModule:: Mutex _mutex ;
};
const std::string default_filename = "log.txt" ;
const std::string default_filepath = "./log" ;//./log/log.txt
class FileLogStrategy : public LogFlashStrategy
{
public:
FileLogStrategy(const std::string file = default_filename ,
const std::string path = default_filepath )
:_filename(file),
_filepath(path)
{
MutexModule::MutexGuard _MutexGuard(_mutex);
if(std::filesystem :: exists(_filepath))
{
return ;
}
try
{
std::filesystem:: create_directories(_filepath);
}
catch(const std::filesystem::filesystem_error& _exception)
{
std::cout<<_exception.what()<<CAGE ;
}
}
void SyncLog(const std::string& logmessage)
{
MutexModule::MutexGuard _MutexGuard(_mutex);
std::string pathfile = _filepath +
(_filepath.back() == '/' ? "" : "/") + _filename ;
std :: ofstream out (pathfile , std::ios::app);
if(!out.is_open())
{
return ;
}
out<<logmessage<<CAGE;
out.close();
}
private:
MutexModule::Mutex _mutex ;
std::string _filename ;
std::string _filepath ;
};
enum class LEVEL
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string GetCurrentTime()
{
time_t cur_time = time(nullptr);
struct tm result ;
localtime_r(&cur_time ,&result );
char timebuffer[128];
snprintf(timebuffer , sizeof(timebuffer) , "%4d-%02d-%02d %02d:%02d:%02d",
result.tm_year + 1900,
result.tm_mon + 1,
result.tm_mday,
result.tm_hour,
result.tm_min,
result.tm_sec
);
return timebuffer ;
}
std::string LevelToString(LEVEL level)
{
switch (level)
{
case LEVEL::DEBUG:
return "DEBUG" ;
case LEVEL::INFO :
return "INFO" ;
case LEVEL::WARNING :
return "WARNING" ;
case LEVEL::ERROR :
return "ERROR" ;
case LEVEL::FATAL :
return "FATAL";
default :
return "UNKNOWORNING";
}
}
class Logger
{
public:
Logger()
{
EnableConsolLogStrategy();
}
void EnableConsolLogStrategy()
{
_current_flash_tsrategy = std::make_unique<ConsoleLogStrategy>();
}
void EnableFileLogStrategy()
{
_current_flash_tsrategy = std::make_unique<FileLogStrategy>();
}
class LogMessage
{
public:
LogMessage(LEVEL& level , std::string& file , size_t line , LogModule::Logger& logger)
:_current_time(GetCurrentTime()),
_level(level),
_pid(getpid()),
_src_file(file),
_line(line),
_logger(logger)
{
std::stringstream ret ;
ret<<"["<<_current_time<<"]"
<<"["<<LevelToString(_level)<<"]"
<<"["<<_pid<<"]"
<<"["<<_src_file<<"]"
<<"["<<_line<<"] -";
_hole_message = ret.str() ;
}
template<class T>
LogMessage& operator<< (const T& info)
{
std::stringstream ss ;
ss <<info ;
_hole_message += ss.str() ;
return *this ;
}
~LogMessage()
{
if(_logger._current_flash_tsrategy)
_logger._current_flash_tsrategy->SyncLog(_hole_message);
}
private:
std::string _current_time ;
LEVEL _level ;
pid_t _pid ;
std::string _src_file ;
size_t _line ;
std::string _hole_message ;
LogModule::Logger& _logger ;
};
LogMessage operator()(LEVEL level , std::string filename , size_t line )
{
return LogMessage(level , filename ,line,*this);
}
private:
std::unique_ptr<LogFlashStrategy> _current_flash_tsrategy ;
};
Logger logger ;
#define LOG(level) logger(level , __FILE__ , __LINE__)
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.EnableConsolLogStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.EnableFileLogStrategy()
}
#endif
3.1.4Task.hpp
cpp
#ifndef __TASK__
#define __TASK__
#include<functional>
#include<iostream>
using task_t = std::function<void(void)>;
void DownLoadTask(void)
{
std::cout<<"这是一个下载任务"<<std::endl;
}
#endif
3.2主要组件
3.2.1Thread.hpp
cpp
#include "Mutex.hpp"
#include "Task.hpp"
#include "Log.hpp"
namespace ThreadModule
{
int count = 1 ;
class Thread
{
private:
static void* Routine(void* args)
{
Thread* self = static_cast<Thread*>(args);
pthread_setname_np(pthread_self(),self->_name);
if(!self->_isrunning)
self->_isrunning = true ;
self->_func();//转化成了this->_func();
return nullptr ;
}
public:
Thread(task_t task)
:_tid(0),
_func(task),
_isrunning(false)
{
snprintf(_name , sizeof(_name),"Thread-%d",count);
count++ ;
}
void Start()
{
if(_isrunning)
{
return ;
}
int n = pthread_create(&_tid,nullptr,&Routine , this);
if(n == 0) LogModule::LOG(LogModule::LEVEL::DEBUG)<<"线程创建成功"<<LogModule::CAGE;
else LogModule::LOG(LogModule::LEVEL::ERROR)<<"线程闯进啊失败"<<LogModule::CAGE;
}
void join()
{
pthread_join(_tid,nullptr);
}
char* Name()
{
return _name ;
}
private:
pthread_t _tid ;
bool _isrunning ;
task_t _func ;
char _name[128] ;
};
}
3.2.2ThreadPool.hpp
cpp
#ifndef __THREAD_POOL__
#define __THREAD_POOL__
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Task.hpp"
#include <queue>
namespace ThreadPoolModuleBasedOnSingletonPattern
{
using namespace LogModule;
using namespace MutexModule;
using namespace ConModule;
using namespace ThreadModule;
static const size_t DEFULT_SIZE = 5;
template <typename T>
class ThreadPool
{
private:
void WeakUpAllThread()
{
if (_ThreadSleepSize)
_cond.Broadcast();
LOG(LEVEL::INFO) << "唤醒所有休眠的线程" << CAGE;
}
void WeakUpOneThread()
{
_cond.Signal();
LOG(LEVEL::INFO) << "唤醒一个休眠线程" << CAGE;
}
ThreadPool()
: _ThreadSleepSize(0),
_isrunning(false),
_Size(DEFULT_SIZE)
{
for (int i = 0; i < DEFULT_SIZE; i++)
{
_Thread.emplace_back(
[this]()
{
HanderTask();
});
}
}
void Start()
{
if (_isrunning)
return;
_isrunning = true;
for (auto &e : _Thread)
{
e.Start();
LOG(LEVEL::INFO) << "创建线程池成功" << e.Name();
}
}
ThreadPool<T>(const ThreadPool<T> &threadpool) = delete;
ThreadPool<T> &operator=(const ThreadPool<T> &threadpool) = delete;
public:
static ThreadPool<T> *GetInstance()
{
if (_SingPtr == nullptr)
{
MutexGuard mutexguard(_lock);
LOG(LEVEL::INFO) << "获取单例。。。" << CAGE;
{
if (_SingPtr == nullptr)
{
LOG(LEVEL::INFO) << "首次使用单例,创建单例" << CAGE;
_SingPtr = new ThreadPool<T>;
_SingPtr->Start();
}
}
}
return _SingPtr;
}
void HanderTask()
{
LOG(LEVEL::INFO) << "进入线程处理函数";
char name[64];
pthread_getname_np(pthread_self(), name, sizeof(name));
while (true)
{
T task;
{
MutexGuard mutexguard(_mutex);
while (_isrunning && _TaskQueue.empty())
{
LOG(LEVEL::DEBUG) << "进入等待";
_ThreadSleepSize++;
_cond.Wait(_mutex);
_ThreadSleepSize--;
LOG(LEVEL::DEBUG) << "被唤醒";
}
LOG(LEVEL::INFO)
<< "退出等待 queue="
<< _TaskQueue.size();
if (!_isrunning && _TaskQueue.empty())
{
LOG(LEVEL::INFO) << "线程池退出&7任务队列为空" << CAGE;
break;
}
task = _TaskQueue.front();
LOG(LEVEL::INFO) << "取出任务";
_TaskQueue.pop();
}
task();
}
}
bool Equeue(const T &task)
{
if (_isrunning)
{
MutexGuard mutexguard(_mutex);
_TaskQueue.push(task);
if (_ThreadSleepSize > 0)
WeakUpOneThread();
return true;
}
return false;
}
void Stop()
{
LOG(LEVEL::DEBUG)<<"线程池停止"<<CAGE;
if (_isrunning)
{
MutexGuard mutexguard(_mutex);
_isrunning = false;
if (_ThreadSleepSize)
{
WeakUpAllThread();
}
}
}
void Join()
{
LOG(LEVEL::DEBUG)<<"线程池回收线程"<<CAGE;
if (!_isrunning)
{
for (auto &e : _Thread)
{
e.join();
}
}
}
private:
size_t _ThreadSleepSize;
bool _isrunning;
std::vector<Thread> _Thread;
std::queue<T> _TaskQueue;
size_t _Size; // 线程个数
Mutex _mutex;
Cond _cond;
static ThreadPool<T> *_SingPtr;
static Mutex _lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::_SingPtr = nullptr;
template <class T>
Mutex ThreadPool<T>::_lock;
}
#endif
3.3构建与测试
3.3.1Makefile
bash
BIN = proc
SRC = Main.cpp
$(BIN) : $(SRC)
g++ -o $@ $^ -pthread -std=c++17
.PHONY:
clean :
rm -f $(BIN) ./log/log.txt
3.3.2Main.cpp
cpp
#include "ThreadPool.hpp"
using namespace LogModule ;
using namespace ThreadPoolModuleBasedOnSingletonPattern;
const size_t NUM = 5 ;
int main()
{
ENABLE_FILE_LOG_STRATEGY();
for(int i = 0 ; i < NUM ;i ++)
{
sleep(1);
ThreadPool<task_t> :: GetInstance()->Equeue(DownLoadTask);
}
ThreadPool<task_t> :: GetInstance()->Stop();
ThreadPool<task_t> :: GetInstance()->Join();
return 0;
}
好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。