跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

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

综述由AI生成使用 C++ 手写线程池的全过程。内容包括 ThreadPool 类的设计,涉及构造函数、启动接口及任务队列管理。重点讲解了线程回调函数的实现机制,包括 Lambda 表达式捕获 this 指针及 pthread_create 的参数传递。此外,深入分析了线程池的生命周期管理,如 Stop 接口的优化逻辑,确保线程在退出前处理完剩余任务。文章还探讨了线程安全与重入性的概念区别,以及死锁产生的四个必要条件及其避免策略。最后简要说明了 STL 容器与智能指针在线程环境下的安全性问题。

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

实现线程池

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

ThreadPool 类设计

构造函数

启动线程池分为两步:创建线程对象和启动线程。在构造函数中创建 Thread 对象,传递回调函数和线程名。由于成员函数默认包含 this 指针,而封装的 Thread 类回调设计为无参无返回值,需使用 Lambda 表达式或包装器解决参数匹配问题。

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);
        _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();
    }
}

线程池接入日志

在线程创建、启动、等待、回收等关键步骤输出日志,便于调试。

初步实现源码及效果图

ThreadPool.hpp

#pragma once
#include <iostream>
#include <queue>
#include 





   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;

    {
         _q.();
    }
};
<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
NotifyAll
void Wait()
for
auto
Join
void Enqueue(const T &t)
if
return
LockGuard lockguard(&_lock)
push
if
0
NotifyOne
ThreadPool
private
int
int
bool
bool QueueIsEmpty()
return
empty

main.cpp

#include "ThreadPool.hpp"
#include <memory>

int main()
{
    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 对象指针,pthread_routine 将其强转为 Thread*,调用存储的 Lambda 函数,最终执行 ThreadPool::Routine。

实现回调函数 Routine

真正的回调函数应从任务队列获取任务并消费。访问临界资源(如任务队列)需加锁,但任务执行本身无需加锁。

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)
    {
        _cond.NotifyAll();
    }
}

线程安全和重入问题

线程安全:多个线程访问共享资源时能正确执行,互不干扰。通常涉及全局变量或静态变量且无锁保护时易出问题。

重入:同一函数被不同执行流调用,前一个流程未结束,另一个再次进入。若结果不受影响则为可重入函数。

结论

  1. 可重入函数一定是线程安全的。
  2. 线程安全不一定是可重入的(例如单进程响应信号场景下可能产生死锁)。
  3. 含有全局变量的函数既不是线程安全也不是可重入的。
  4. 对临界资源加锁可使函数线程安全,但若锁未释放即重入则会产生死锁,导致不可重入。

死锁

死锁是指一组进程中各进程均占有不会释放的资源,因互相申请被其他进程占用的资源而处于永久等待状态。

死锁四个必要条件

  1. 互斥条件:资源每次只能被一个执行流使用。
  2. 请求与保持条件:执行流因请求资源阻塞时,对已获资源保持不放。
  3. 不剥夺条件:已获得资源在使用完之前不能被强行剥夺。
  4. 循环等待条件:若干执行流之间形成头尾相接的循环等待关系。

避免死锁

核心思想是破坏上述一个或多个必要条件:

  1. 破坏请求与保持条件:使用 pthread_mutex_trylock,申请锁失败后不阻塞而是返回错误码,释放已持锁重新申请。
  2. 破坏不剥夺条件:设计仲裁函数,优先级较低的线程主动释放锁。
  3. 预防循环等待:保证资源一次性分配,使用超时机制,或统一加锁顺序。

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. 死锁
  14. 死锁四个必要条件
  15. 避免死锁
  16. STL、智能指针和线程安全
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • WSL2 环境下 AI 助手调用摄像头方案实践
  • Web3 社区运营指南:构建、激励与增长策略
  • C++ 初学者核心概念与常见误区解析
  • World Monitor 开源全球情报仪表盘
  • Claude Code 命令行工具安装与环境配置指南
  • 文心一言与通义千问大模型能力评测
  • CLI-Anything:让所有软件都能被 AI Agent 原生调用
  • OpenClaw 个人 AI 助理跨平台安装与配置教程
  • Stable Diffusion 底模 VAE 推荐与配置指南
  • 合并两个升序链表与合并 K 个升序链表
  • Stable Diffusion 秋叶整合包本地安装与使用指南
  • 实战指南:Stable Diffusion 模型部署问题排查与性能调优
  • MCP 插件配置指南:以 browser-tools-mcp 为例
  • Mac 远程连接 Windows 简明教程
  • DeepSeek-R1 模型基于 LLaMA-Factory 的可视化微调实战
  • 基于 ECharts 与 Three.js 的碳排放可视化大屏实现
  • Docker Logs 命令:从基础到高级日志管理
  • LLaMA-Factory 项目介绍与安装部署指南
  • AI Agent 中的 Skills 概念与核心作用
  • 基于 Uniapp 与 SSM 的鞋类清洗管理 App 金融功能设计

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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