☰
ThreadPool-master 多线程开源项目浅析-1
2026/9/30 2:41:03 网站建设 项目流程

项目地址:https://github.com/progschj/ThreadPool

一、项目介绍

本项目是一个经典的C++11 单头文件线程池,代码量约 100 行,是最基础的线程池实现,没有太多复杂的功能,涵盖以下核心概念:

类别涉及的知识点
多线程std::thread、std::mutex、std::unique_lock、std::condition_variable
异步编程std::future、std::packaged_task、std::promise
泛型编程变参模板class... Args、完美转发std::forward、尾置返回类型
函数包装std::function、std::bind、lambda 表达式、类型擦除
内存管理std::make_shared、std::move、RAII
STL 容器std::vector、std::queue、emplace_back

二、项目源码

2.1 ThreadPool.h

#ifndefTHREAD_POOL_H#defineTHREAD_POOL_H#include<vector>#include<queue>#include<memory>#include<thread>#include<mutex>#include<condition_variable>#include<future>#include<functional>#include<stdexcept>classThreadPool{public:ThreadPool(size_t);template<classF,class...Args>autoenqueue(F&&f,Args&&...args)->std::future<typenamestd::result_of<F(Args...)>::type>;~ThreadPool();private:// need to keep track of threads so we can join themstd::vector<std::thread>workers;// 工作线程对象// the task queuestd::queue<std::function<void()>>tasks;// 任务队列// synchronizationstd::mutex queue_mutex;std::condition_variable condition;boolstop;};// the constructor just launches some amount of workersinlineThreadPool::ThreadPool(size_t threads):stop(false){for(size_t i=0;i<threads;++i)workers.emplace_back([this]{for(;;){std::function<void()>task;{std::unique_lock<std::mutex>lock(this->queue_mutex);this->condition.wait(lock,[this]{returnthis->stop||!this->tasks.empty();});if(this->stop&&this->tasks.empty())return;task=std::move(this->tasks.front());this->tasks.pop();}task();}});}// add new work item to the pooltemplate<classF,class...Args>autoThreadPool::enqueue(F&&f,Args&&...args)->std::future<typenamestd::result_of<F(Args...)>::type>{usingreturn_type=typenamestd::result_of<F(Args...)>::type;autotask=std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f),std::forward<Args>(args)...));std::future<return_type>res=task->get_future();{std::unique_lock<std::mutex>lock(queue_mutex);// don't allow enqueueing after stopping the poolif(stop)throwstd::runtime_error("enqueue on stopped ThreadPool");tasks.emplace([task](){(*task)();});}condition.notify_one();returnres;}// the destructor joins all threadsinlineThreadPool::~ThreadPool(){{std::unique_lock<std::mutex>lock(queue_mutex);stop=true;}condition.notify_all();for(std::thread&worker:workers)worker.join();}#endif

2.2 main.cpp

#include<iostream>#include<vector>#include<chrono>#include"ThreadPool.h"intmain(){ThreadPoolpool(4);std::vector<std::future<int>>results;for(inti=0;i<8;++i){results.emplace_back(pool.enqueue([i]{std::cout<<"hello "<<i<<std::endl;std::this_thread::sleep_for(std::chrono::seconds(10));std::cout<<"world "<<i<<std::endl;returni*i;}));}for(auto&&result:results)std::cout<<result.get()<<"hh"<<std::endl;std::cout<<std::endl;return0;}

三、代码架构

3.1 总览图

┌─────────────────────────────────────────────────────────────────────┐ │ ThreadPool │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ 构造函数 Constructor │ │ │ │ 根据参数 threads 创建 N 个工作线程,每个线程进入事件循环 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │ │ │ workers (线程池) │ │ tasks (任务队列) │ │ 同步原语 │ │ │ │ vector<thread> │ │ queue<func()> │ │ mutex + cv │ │ │ │ │ │ │ │ + stop flag │ │ │ │ [Worker-0] │ │ ┌────────────┐ │ │ │ │ │ │ [Worker-1] │ │ │ Task #0 │ │ │ │ │ │ │ [Worker-2] │ │ ├────────────┤ │ │ │ │ │ │ [Worker-3] │ │ │ Task #1 │ │ │ │ │ │ │ │ │ ├────────────┤ │ │ │ │ │ │ │ │ │ Task #2 │ │ │ │ │ │ │ │ │ ├────────────┤ │ │ │ │ │ │ │ │ │ ... │ │ │ │ │ │ │ │ │ └────────────┘ │ │ │ │ │ └────────┬─────────┘ └────────┬─────────┘ └───────────────┘ │ │ │ │ │ │ │ 竞争获取任务 │ │ │ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘

3.2 类型擦除机制(核心技巧)

这是该线程池设计中最巧妙的部分:

用户提交的任务 (各种类型) 队列中统一存储 ┌──────────────────────┐ ┌─────────────────┐ │ int func(int x) │ │ │ │ string func(string) │ ──包装──► │ function<void()>│ │ void func(A, B, C) │ │ │ │ auto lambda │ └─────────────────┘ └──────────────────────┘ │ │ 通过 packaged_task + bind + lambda │ 将 "有参数有返回值" 的函数 │ 包装成 "无参数无返回值" 的可调用对象 ▼ ┌────────────────────────────────────────────────────────┐ │ std::bind(std::forward<F>(f), std::forward<Args>(args)...) │ │ │ │ ▼ │ │ std::packaged_task<return_type()> │ │ │ ① 内部存储了 bind 后的可调用对象 │ │ │ ② 提供了 get_future() 获取返回值 │ │ ▼ │ │ [task]{ (*task)(); } // lambda: 执行并存储结果到future │ │ │ │ │ ▼ │ │ std::function<void()> // 统一类型,存入队列 │ └────────────────────────────────────────────────────────┘

3.3 线程间通信机制

主线程 (Main Thread) │ │ pool.enqueue(task) │ ▼ ┌────────────────────┐ │ 共享任务队列 │ ← 由 mutex 保护 │ queue<func()> │ └───────┬────────────┘ │ condition_variable .notify_one() 通知 │ ┌────────┼────────┬────────┐ │ │ │ │ ▼ ▼ ▼ ▼ Worker0 Worker1 Worker2 Worker3 │ │ │ │ │ 竞争获取 mutex 锁,只有一个能拿到任务 │ 拿到锁后从队列取出任务,立即释放锁 │ 然后各自并行执行任务 │ │ │ │ ▼ ▼ ▼ ▼ task() task() task() task() │ │ │ │ ▼ ▼ ▼ ▼ future future future future ← 结果通过 future 传回主线程 └────────┴────────┴────────┘ │ ▼ 主线程调用 result.get() 获取结果

3.4 析构流程(优雅关闭)

析构函数 ~ThreadPool() │ ▼ ┌─────────────────────┐ │ ① 设置 stop = true │ (持有锁) └─────────┬───────────┘ ▼ ┌─────────────────────┐ │ ② condition │ │ .notify_all() │ ← 唤醒所有阻塞的 Worker └─────────┬───────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ ③ 每个 Worker 被唤醒后: │ │ → 检查条件: stop == true && 队列为空 │ │ → 从事件循环中 return,线程函数结束 │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────┐ │ ④ worker.join() │ ← 主线程等待所有 Worker 退出 │ (对每个线程) │ └─────────────────────┘ │ ▼ 线程池安全销毁 ✓

四、知识点详细梳理

4 .1std::function— 函数包装器 / 类型擦除

头文件:<functional>

4.1.1 什么是 std::function?

std::function 是 C++11 引入的一个万能函数包装器,定义在 头文件中。它的核心能力是:用一个统一的类型,包装任意可调用对象。

它可以包装什么?

#include<functional>#include<iostream>// 1. 普通函数voidfreeFunc(){std::cout<<"free function\n";}// 2. 函数对象(仿函数)structFunctor{voidoperator()()const{std::cout<<"functor\n";}};// 3. 全部塞进同一个 std::function 变量std::function<void()>f;f=freeFunc;// ✅ 普通函数指针f=Functor();// ✅ 函数对象f=[]{std::cout<<"lambda\n";};// ✅ Lambda 表达式f=std::bind(freeFunc);// ✅ std::bind 结果

要点: 无论原始可调用对象的"出身"是什么——函数指针、lambda、仿函数、std::bind 结果——只要调用签名是void(),就都能塞进std::function<void()>。

4.1.2类型擦除(Type Erasure)是什么?

问题:异构类型如何统一管理?
考虑这样的场景:

// 用户可能提交各种不同签名的任务:inttask1(inta,intb);// 签名: int(int, int)voidtask2(conststd::string&s);// 签名: void(const string&)doubletask3(doublex,doubley);// 签名: double(double, double)// 如果不用类型擦除,你如何把它们放进同一个容器?std::queue<???>tasks;// 放什么类型?不可能同时是 int(int,int)、void(string)、double(double,double)

类型擦除的答案: 把所有这些不同的可调用对象,统一转换为std::function<void()>—— 即"不接受参数、不返回值的函数对象"。这样队列的类型就统一了。

问题:那参数和返回值去哪了?

这就是关键技巧:参数和返回值在入队之前就通过 std::bind + std::packaged_task 绑定好了,剩下的只是一个"按一下就能执行"的无参函数对象。

在你的 ThreadPool 项目中的具体应用:

//任务队列的定义//ThreadPool.hstd::queue<std::function<void()>>tasks;// 任务队列,统一为 void() 签名这就是整个线程池的"心脏"。无论用户提交什么任务,最终都变成一个个 std::function<void()>躺在队列里。
// enqueue 方法:类型擦除的完整过程//ThreadPool.h://这是整个项目中最值得细品的代码,我们逐行拆解:template<classF,class...Args>autoThreadPool::enqueue(F&&f,Args&&...args)->std::future<typenamestd::result_of<F(Args...)>::type>{usingreturn_type=typenamestd::result_of<F(Args...)>::type;// 🔑 第1步:创建 packaged_task,绑定用户函数和参数autotask=std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f),std::forward<Args>(args)...));// 🔑 第2步:获取 future,之后返回给用户std::future<return_type>res=task->get_future();{std::unique_lock<std::mutex>lock(queue_mutex);if(stop)throwstd::runtime_error("enqueue on stopped ThreadPool");// 🔑 第3步:类型擦除!将 packaged_task 包装成 std::function<void()>tasks.emplace([task](){(*task)();});}condition.notify_one();returnres;// 返回 future 给用户,用户用它获取结果}

4.1.3 工作线程如何消费任务?

for(;;){std::function<void()>task;// 👈 同样用 std::function<void()> 接收{std::unique_lock<std::mutex>lock(this->queue_mutex);this->condition.wait(lock,[this]{returnthis->stop||!this->tasks.empty();});if(this->stop&&this->tasks.empty())return;task=std::move(this->tasks.front());// 取出任务this->tasks.pop();}task();// 👈 直接调用!不需要知道原始类型}

工作线程完全不知道(也不需要知道)原始任务是什么类型。它只认 std::function<void()>,取出、调用,完事。这就是类型擦除带来的解耦。

五、完整数据流示意图

用户代码: pool.enqueue([](int x) { return x * x; }, 5) │ │ 原始类型: int(int) ▼ std::bind → 绑定参数 5 → 类型变为 int() │ ▼ std::packaged_task<int()> → 包装为可异步获取结果的对象 │ ▼ lambda: [task]() { (*task)(); } → 类型为 匿名lambda,签名 void() │ ▼ std::function<void()> → 类型擦除完成,存入队列 │ ▼ 工作线程取出 task,调用 task() → 执行原始 lambda(5) → 结果写入 future │ ▼ 用户通过 future.get() 拿到结果 25

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询