从零手写线程池:把多线程、锁、同步、日志讲透

经常写 Linux 多线程代码就会发现:线程开多了扛不住,反复创建销毁太浪费,任务来了再临时起线程又太慢。

真正能打的服务,几乎都离不开线程池

但很多人一上手就被一堆概念绕晕:互斥、临界区、条件变量、生产者消费者、单例、线程安全...... 看着就头大。

其实线程池一点都不玄乎,它就是一套 **"固定工人 + 任务排队 + 有人睡觉有人干活 + 安全退出"** 的工作模式。

今天我就用最通俗、最接地气的方式,带你从零撸一个带完整源码、带日志、带单例、能直接上线用 的 C++ 线程池。所有代码都是我亲手实现的,可直接编译运行。


一、先把话说明白:线程池到底是个啥?

你可以把线程池想象成一个小工厂

  • 工厂里提前雇好 5 个固定工人(线程)
  • 门口放一个 任务架子(任务队列)
  • 老板不断把任务放上去(生产者)
  • 工人轮流过来拿任务、做完再回来拿(消费者)
  • 没任务时,工人就在旁边睡觉等,不瞎转悠浪费电
  • 要关门下班时,先把架子上的任务做完,再统一下班

这就是线程池最朴素的本质:提前开好一批线程,循环等待任务,来了就做,没有就等,用完不销毁,反复利用。

它解决三件事:

  1. 避免频繁创建 / 销毁线程的巨大开销
  2. 控制线程数量,防止把系统拖崩
  3. 让任务处理更快、更稳定

二、整体架构:我写了哪些核心模块?

我把线程池拆成 5 个完全独立的头文件,结构清晰、低耦合、方便移植:

  1. Mutex.hpp ------ 互斥锁 + RAII 自动锁
  2. Cond.hpp ------ 条件变量(等待 / 唤醒)
  3. Thread.hpp ------ 线程类封装(创建、等待、分离、停止)
  4. Log.hpp ------ 策略模式日志系统(控制台 / 文件双输出)
  5. 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 设计亮点

  1. 固定 5 个线程
  2. 任务队列std::queue
  3. 生产者 - 消费者模型
  4. 线程安全(锁 + 条件变量)
  5. 单例模式(全局唯一)
  6. 优雅退出(处理完任务再退出)
  7. 休眠线程计数(精准唤醒)

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了解具体过程

相关推荐
gihigo19982 分钟前
30节点系统最优潮流计算(MATLAB实现)
开发语言·matlab
꧁细听勿语情꧂22 分钟前
向下调整算法,top - k 问题,链式结构二叉树,前中后序遍历
c语言·开发语言·数据结构·算法
橘颂TA1 小时前
【Linux】自旋锁
linux·开发语言·数据库·c++
一诺加油鸭1 小时前
若依后端系统集成 Swagger 接口文档功能
java·开发语言
ECT-OS-JiuHuaShan1 小时前
功夫不负匠心人,渡劫代谢舞沧桑
android·开发语言·人工智能·算法·机器学习·kotlin·拓扑学
knight_9___1 小时前
LLM工具调用面试篇1
开发语言·人工智能·python·面试·agent
珹洺1 小时前
C++AI多模型聊天系统(一)项目背景意义与整体架构、核心基类实现
c++·人工智能·架构
一脸dio样7541 小时前
第5章 保护模式进阶,向内核迈进
linux·开发语言
智者知已应修善业2 小时前
【51单片机ADC-MAX1241/ADC0832驱动】2023-6-6
c++·经验分享·笔记·算法·51单片机
小叮当⇔2 小时前
M4A 转 MP3 桌面转换器(PyQt5 + FFmpeg)
开发语言·qt·ffmpeg