经常写 Linux 多线程代码就会发现:线程开多了扛不住,反复创建销毁太浪费,任务来了再临时起线程又太慢。
真正能打的服务,几乎都离不开线程池。
但很多人一上手就被一堆概念绕晕:互斥、临界区、条件变量、生产者消费者、单例、线程安全...... 看着就头大。
其实线程池一点都不玄乎,它就是一套 **"固定工人 + 任务排队 + 有人睡觉有人干活 + 安全退出"** 的工作模式。
今天我就用最通俗、最接地气的方式,带你从零撸一个带完整源码、带日志、带单例、能直接上线用 的 C++ 线程池。所有代码都是我亲手实现的,可直接编译运行。
一、先把话说明白:线程池到底是个啥?
你可以把线程池想象成一个小工厂:
- 工厂里提前雇好 5 个固定工人(线程)
- 门口放一个 任务架子(任务队列)
- 老板不断把任务放上去(生产者)
- 工人轮流过来拿任务、做完再回来拿(消费者)
- 没任务时,工人就在旁边睡觉等,不瞎转悠浪费电
- 要关门下班时,先把架子上的任务做完,再统一下班
这就是线程池最朴素的本质:提前开好一批线程,循环等待任务,来了就做,没有就等,用完不销毁,反复利用。
它解决三件事:
- 避免频繁创建 / 销毁线程的巨大开销
- 控制线程数量,防止把系统拖崩
- 让任务处理更快、更稳定
二、整体架构:我写了哪些核心模块?
我把线程池拆成 5 个完全独立的头文件,结构清晰、低耦合、方便移植:
Mutex.hpp------ 互斥锁 + RAII 自动锁Cond.hpp------ 条件变量(等待 / 唤醒)Thread.hpp------ 线程类封装(创建、等待、分离、停止)Log.hpp------ 策略模式日志系统(控制台 / 文件双输出)ThreadPool.hpp------ 线程池本体(单例、任务队列、消费逻辑)
下面我会带着源码,一行一行讲清楚设计思路。
三、模块一:Mutex.hpp ------ 锁,就是线程的 "红绿灯"
多线程最容易乱的根源:大家同时抢一个共享资源 。我的解决方案:封装原生锁 + RAII 自动管理。
#pragma once
#include <iostream>
#include <pthread.h>
namespace yzq
{
// 封装原生互斥锁
class Mutex
{
public:
Mutex() { pthread_mutex_init(&_mutex, nullptr); }
void Lock() { pthread_mutex_lock(&_mutex); }
void UnLock() { pthread_mutex_unlock(&_mutex); }
pthread_mutex_t *Get() { return &_mutex; }
~Mutex() { pthread_mutex_destroy(&_mutex); }
private:
pthread_mutex_t _mutex;
};
// RAII 自动锁:创建加锁,析构解锁,绝对不会忘
class LockGuard
{
public:
LockGuard(Mutex &mutex) : _mutex(mutex) { _mutex.Lock(); }
~LockGuard() { _mutex.UnLock(); }
private:
Mutex &_mutex;
};
};
人话讲解
- Mutex :对
pthread_mutex_t做面向对象封装,提供加锁、解锁。 - LockGuard :最关键的设计!代码块开始 → 自动加锁 代码块结束 → 自动解锁
- 再也不用担心忘记解锁导致死锁。
四、模块二:Cond.hpp ------ 让线程 "会睡觉、会被叫醒"
只有锁还不够:没任务时,线程不能一直while(1)空转,CPU 会直接 100%!
正确做法:没任务睡觉,有任务被唤醒。
#pragma once
#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
using namespace yzq;
namespace yzq
{
class Cond
{
public:
Cond() { pthread_cond_init(&_cond, nullptr); }
// 等待:会自动释放锁,被唤醒后重新持有锁
void Wait(Mutex &mutex) { pthread_cond_wait(&_cond, mutex.Get()); }
// 唤醒一个
void Signal() { pthread_cond_signal(&_cond); }
// 唤醒全部
void Broadcast() { pthread_cond_broadcast(&_cond); }
~Cond() { pthread_cond_destroy(&_cond); }
private:
pthread_cond_t _cond;
};
};
关键点
Wait 是线程同步的灵魂:
- 调用时自动释放锁
- 休眠等待
- 被唤醒后自动重新获取锁
这一步错了,你的线程池永远写不对。
五、模块三:Thread.hpp ------ 把线程包装成 "好用的工具"
我不想每次都写 pthread_create,太麻烦。我直接封装一个线程类,支持:创建、启动、停止、分离、等待、设置名字。
static uint32_t number = 1;
class Thread
{
using func_t = std::function<void()>;
void EnableDetach()
{
std::cout << "线程被分离了" << std::endl;
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args) // 用static修饰,因为如果是类内函数其第一个参数为this
{
Thread *self = static_cast<Thread *>(args);
self->EnableRunning(); // 创建后直接运行
if (self->_isdetach)
{
self->Detach();
}
pthread_setname_np(self->_tid, self->_name.c_str());
self->_func(); // 回调
return nullptr;
}
public:
Thread(func_t func)
: _tid(0), _isdetach(false), _isrunning(false), _res(nullptr), _func(func)
{
_name = "thread-" + std::to_string(number++);
}
// 启动线程
bool Start()
{
if (_isrunning)
{
return false;
}
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0) // 创建失败
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << _name << " create success" << std::endl;
return true;
}
}
// 分离线程
bool Detach()
{
if (_isdetach)
return false;
if (_isrunning)
pthread_detach(_tid);
EnableDetach();
return true;
}
// 中止线程
bool Stop()
{
if (!_isrunning)
return false;
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
_isrunning = false;
std::cout << _name << " stop" << std::endl;
return true;
}
}
// 回收线程
bool Join()
{
if (_isdetach)
{
std::cout << "你的线程已经是分离的了,不能进行join" << std::endl;
return false;
}
int n = pthread_join(_tid, &_res);
if (n != 0)
{
std::cerr << "create thread error: " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << "join success" << std::endl;
return true;
}
}
std::string Name()
{
return _name;
}
~Thread() {}
private:
pthread_t _tid; // 线程号
std::string _name; // 线程名
bool _isdetach; // 分离状态
bool _isrunning; // 运行状态
void *_res; // 线程返回值
func_t _func; // 线程执行的函数
};
作用
- 支持直接传入
lambda表达式 - 自动命名线程:
thread-1、thread-2... - 状态管理:是否运行、是否分离
六、模块四:Log.hpp ------ 线程池的 "黑匣子"
没有日志的多线程程序 = 玄学调试。我用策略模式写了一套极简、强大、线程安全的日志。
核心设计
-
日志等级:DEBUG / INFO / WARNING / ERROR / FATAL
-
双输出策略:控制台 / 文件
-
自动携带信息:时间、PID、文件名、行号
-
使用超级简单 :
LOG(INFO) << "线程启动成功";// 策略基类
class LogStrategy {
public:
virtual void SyncLog(const std::string message) = 0;
};// 控制台输出
class ConsoleLogStrategy : public LogStrategy;
// 文件输出
class FileLogStrategy : public LogStrategy;// 全局日志对象
Logger logger;// 宏定义,一行打印
#define LOG(level) logger(level, FILE, LINE)
日志效果
[2026-04-04 17:22:33] [INFO] [1234] [ThreadPool.hpp] [30] - start new thread success: thread-1
七、核心模块:ThreadPool.hpp ------ 真正的线程池
7.1 设计亮点
- 固定 5 个线程
- 任务队列
std::queue - 生产者 - 消费者模型
- 线程安全(锁 + 条件变量)
- 单例模式(全局唯一)
- 优雅退出(处理完任务再退出)
- 休眠线程计数(精准唤醒)
7.2 核心逻辑:工人线程怎么干活?
void HandlerTask()
{
while (true)
{
T t;
{
LockGuard lockguard(_mutex); // 自动加锁
// 没任务 && 还在运行 → 睡觉
while (_taskq.empty() && _isrunning)
{
_sleepernum++;
_cond.Wait(_mutex); // 等任务
_sleepernum--;
}
// 没任务 && 要退出 → 下班
if (_taskq.empty() && !_isrunning)
{
break;
}
// 取任务
t = _taskq.front();
_taskq.pop();
}
t(); // 【关键】在锁外执行任务!
}
}
为什么要在锁外执行任务?
锁只保护队列访问 ,不保护任务执行 。如果锁内执行,就变成串行执行,并发效率直接归零!
7.3 提交任务:老板放任务
bool Enqueue(const T &in)
{
LockGuard lockguard(_mutex);
if (_isrunning)
{
_taskq.push(in);
// 如果所有线程都在睡觉,叫醒一个来干活
if (_threads.size() == _sleepernum)
WakeUpOne();
return true;
}
return false;
}
7.4 单例模式:全局只有一个线程池
static ThreadPool *GetInstance()
{
LockGuard lockguard(_lock);
if (inc == nullptr)
{
inc = new ThreadPool<T>();
inc->Start();
}
return inc;
}
保证:
- 全局唯一
- 线程安全
- 懒加载(用到才创建)
八、运行效果(日志实录)
[2026-04-04 17:30:00] [INFO] [14567] [ThreadPool.hpp] [30] - start new thread success: thread-1
[2026-04-04 17:30:00] [INFO] [14567] [ThreadPool.hpp] [30] - start new thread success: thread-2
[2026-04-04 17:30:01] [DEBUG] [14567] [main.cpp] [20] - 获取单例
[2026-04-04 17:30:01] [INFO] [14567] [ThreadPool.hpp] [50] - 唤醒单个休眠线程
[2026-04-04 17:30:12] [INFO] [14567] [ThreadPool.hpp] [90] - thread-5 退出了, 线程池退出&&任务队列为空
线程池从来不是什么高大上的黑魔法。
它就是:一堆线程 + 一个队列 + 一把锁 + 一个唤醒机制 + 一套日志 + 一个单例。
想要亲手尝试去深入理解看前往我的gitee:https://gitee.com/fantasy55/linux/tree/master/thread_pool了解具体过程