跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 手写线程池全流程:核心设计、线程安全与死锁解析

综述由AI生成C++ 线程池实现涉及任务队列管理、线程生命周期控制及同步机制。通过封装 ThreadPool 类,结合条件变量与互斥锁,实现任务提交与消费的高效调度。重点解析了线程启动流程、回调函数绑定方式、优雅退出策略以及死锁产生的四个必要条件。同时探讨了 STL 容器与智能指针在线程环境下的安全性问题,为高并发场景下的资源管理提供实践参考。

霸天发布于 2026/3/22更新于 2026/5/75 浏览
C++ 手写线程池全流程:核心设计、线程安全与死锁解析

实现线程池

线程池的核心思路与进程池类似,主要包含一个任务队列和若干工作线程。用户将任务提交到队列中,工作线程从队列获取任务并执行。

线程池架构图

ThreadPool 类设计

启动线程池通常分为两步:先创建线程对象,再启动线程。在构造函数中,我们需要实例化 Thread 对象。这里有一个常见的问题:成员函数作为回调时默认携带 this 指针,而线程封装的回调函数通常设计为无参无返回值的 void() 以解耦。解决此问题有两种常用方式:使用 Lambda 表达式或 std::bind 包装器。

构造函数
ThreadPool(int threadnum = default_threadnum) : _is_running(false), _threadnum(threadnum) {
    for (int i = 0; i < _threadnum; i++) {
        std::string name = "thread-" + std::to_string(i + 1);
        // 使用 Lambda 捕获 this 指针
        _threads.emplace_back([this](const std::string &name) { this->Routine(name); }, name);
    }
}
Start 接口

构造函数仅创建了线程对象,并未真正启动它们。启动逻辑在 Start 方法中完成,同时需防止重复启动。

void Start() {
    if (_is_running) return;
    _is_running = true;
    for (auto &t : _threads) {
        t.Start();
    }
}

Stop 和 Wait 接口的逻辑与此类似,不再赘述。

线程池接入日志

在线程创建、启动、等待及回收的关键节点输出日志,有助于排查运行时问题。ThreadPool 类同样需要遵循这一原则。

初步实现源码及效果图

以下是完整的头文件定义及主程序示例,展示了线程池的基本结构。

// ThreadPool.hpp
# once








   default_threadnum = ;

< T>
  {
:
    {
         () {
            T t;
            {
                ;
                 (() && _is_running) {
                    _wait_thread_num++;
                    _cond.(_lock);
                    _wait_thread_num--;
                }
                 (() && !_is_running) {
                    ;
                }
                t = _q.();
                _q.();
            }
            ();
        }
    }

:
    ( threadnum = default_threadnum) : _is_running(), _threadnum(threadnum), _wait_thread_num() {
         ( i = ; i < _threadnum; i++) {
            std::string name =  + std::(i + );
            _threads.([]( std::string &name) { ->(name); }, name);
        }
    }

    {
         (_is_running) ;
        _is_running = ;
         ( &t : _threads) {
            t.();
        }
    }

    {
         (!_is_running) ;
        _is_running = ;
         (_wait_thread_num > ) {
            _cond.();
        }
    }

    {
         ( &t : _threads) {
            t.();
        }
    }

    {
         (!_is_running) ;
        {
            ;
            _q.(t);
             (_wait_thread_num > ) {
                _cond.();
            }
        }
    }

:
    std::queue<T> _q;
    std::vector<Thread> _threads;
     _threadnum;
     _wait_thread_num;
    Mutex _lock;
    Cond _cond;
     _is_running;
};
pragma
#include <iostream>
#include <queue>
#include <vector>
#include <unistd.h>
#include "Thread.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
const
static
int
3
template
typename
class
ThreadPool
private
void Routine(const std::string &name)
while
1
LockGuard lockguard(&_lock)
while
QueueIsEmpty
Wait
if
QueueIsEmpty
break
front
pop
t
public
ThreadPool
int
false
0
for
int
0
"thread-"
to_string
1
emplace_back
this
const
this
Routine
void Start()
if
return
true
for
auto
Start
void Stop()
if
return
false
if
0
NotifyAll
void Wait()
for
auto
Join
void Enqueue(const T &t)
if
return
LockGuard lockguard(&_lock)
push
if
0
NotifyOne
private
int
int
bool
// main.cc
#include "ThreadPool.hpp"
#include <memory>

int main() {
    EnableConsoleLogStrategy();
    std::unique_ptr<ThreadPool<int>> tp = std::make_unique<ThreadPool<int>>(5);
    tp->Start();
    sleep(5);
    tp->Stop();
    tp->Wait();
    return 0;
}

总结代码执行逻辑

  1. 构造阶段:仅创建 Thread 对象,不启动线程,不执行回调。
  2. 启动阶段:调用 ThreadPool::Start() 触发 Thread::Start(),进而调用 pthread_create。
  3. 回调执行:pthread_create 传入当前 Thread 对象指针,内部通过 static_cast 还原指针,调用存储的 Lambda 表达式,最终执行业务逻辑。

具体流程如下:

// Thread::Start() 核心代码
pthread_create(&_tid, nullptr, pthread_routine, this);

// pthread_routine 内部处理
static void *pthread_routine(void *args) {
    Thread *self = static_cast<Thread *>(args);
    self->_func(self->_name);
    pthread_exit((void *)0);
}

Lambda 表达式捕获了 ThreadPool 的 this 指针,因此能正确访问成员函数 Routine。

实现回调函数 Routine

测试用的 hello 函数过于简单,实际场景中需要从任务队列获取任务。由于涉及临界资源访问,必须加锁,但任务执行本身无需加锁。

void Routine(const std::string &name) {
    while (1) {
        T t;
        {
            LockGuard lockguard(&_lock);
            while (QueueIsEmpty() && _is_running) {
                _wait_thread_num++;
                _cond.Wait(_lock);
                _wait_thread_num--;
            }
            if (QueueIsEmpty() && !_is_running) break;
            t = _q.front();
            _q.pop();
        }
        t();
    }
}

enqueue 接口实现

当用户提交任务后,需唤醒一个休眠线程进行消费。为此引入 _wait_thread_num 记录休眠线程数量。

void Enqueue(const T &t) {
    if (!_is_running) return;
    {
        LockGuard lockguard(&_lock);
        _q.push(t);
        if (_wait_thread_num > 0) {
            _cond.NotifyOne();
        }
    }
}

线程池退出 stop 接口优化

直接强制退出所有线程可能导致任务丢失或状态不一致。优化策略是设置 _is_running 为 false 并唤醒所有休眠线程,让它们在循环检查中自行判断退出。

void Stop() {
    if (!_is_running) return;
    _is_running = false;
    if (_wait_thread_num > 0) {
        _cond.NotifyAll();
    }
}

在 Routine 中增加条件判断:若队列为空且 _is_running 为 false,则退出循环。

线程安全和重入问题

线程安全指多个线程并发访问共享资源时不会相互干扰。重入指同一函数被不同执行流再次调用,前一次尚未结束。

可重入函数通常是线程安全的,但线程安全不一定是可重入的(例如单进程信号处理场景下可能产生死锁)。若函数中包含全局变量且无保护,既非线程安全也非可重入。

死锁

死锁是指多个进程因互相持有对方需要的资源而永久等待的状态。避免死锁的核心在于破坏其四个必要条件之一:

  1. 互斥条件:资源独占。
  2. 请求与保持条件:持有资源的同时请求新资源。
  3. 不剥夺条件:已获资源不能被强制抢占。
  4. 循环等待条件:形成资源等待环。

避免策略:

  • 破坏请求与保持:尝试获取锁失败时立即释放所有锁并重试,如使用 pthread_mutex_trylock。
  • 预防循环等待:规定统一的加锁顺序,或一次性申请所有所需资源。

STL、智能指针和线程安全

STL 容器默认不是线程安全的,设计初衷是追求极致性能,加锁会显著影响效率。多线程环境下使用时需调用者自行保证同步。

智能指针方面,unique_ptr 作用域内有效,不涉及线程安全问题;shared_ptr 的引用计数操作基于原子指令(CAS),保证了多线程下的安全性,但指向的对象本身仍需额外保护。

目录

  1. 实现线程池
  2. ThreadPool 类设计
  3. 构造函数
  4. Start 接口
  5. 线程池接入日志
  6. 初步实现源码及效果图
  7. 总结代码执行逻辑
  8. 实现回调函数 Routine
  9. enqueue 接口实现
  10. 线程池退出 stop 接口优化
  11. 线程安全和重入问题
  12. 死锁
  13. STL、智能指针和线程安全
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Vue Print Designer 前端可视化打印设计器实战解析
  • 开源 UI/UX Pro Max 插件:赋能 AI 生成专业级精美网站
  • MCP 模型上下文协议详解:作用、原理与落地实践
  • Stable Diffusion XL 1.0 灵感画廊镜像免配置部署与使用指南
  • AI 眼镜中的 SD NAND 存储与 SOC 芯片协同架构
  • LLaMA-Factory 微调 GPT-OSS-20B 模型教程(AutoDL+LoRA)
  • AI-Goofish-Monitor:基于 AI 与 Playwright 的闲鱼商品智能监控工具
  • OpenClaw 多 Agent 与飞书机器人配置指南
  • 用Python打造AI三剑客:自动总结+写代码+查资料的完整指南
  • K-means 聚类算法原理与实现详解
  • GitCode 克隆代码认证失败,需改用个人访问令牌或 SSH
  • llama.cpp 量化模型部署实战:从模型转换到 API 服务
  • 职场边界感:平衡工作、生活与职业发展的关键
  • Podman 与 Docker 深度对比及实战指南
  • 数值分析:前向误差与后向误差,好算法为何不怕输入错一点
  • STL 二分查找 lower_bound 与 upper_bound 详解
  • Qwen3-4B 模型部署与写作应用指南
  • Android 自定义节点进度条实现
  • LLaMA-Factory环境配置与WebUI启动全攻略:从CUDA适配到依赖踩坑
  • C++ 泛型编程与模板技术详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online