news 2026/6/10 12:30:56

C++11实战:手把手教你写个线程池

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++11实战:手把手教你写个线程池

自从C++11 直接内置线程库后,再也不用靠操作系统API裸奔写并发了!

但是呢,这标准库给的多线程支持还是太基础了!想整点高级活儿比如线程池、信号量啥的,还得咱自己动手丰衣足食。

比如面试聊到线程池,面试官一问:“说说原理?”

你张口就来:“任务队列 + 线程队列,循环分配呗!”听着贼溜,可真让你写代码?立马原地懵圈——因为C++的std::thread一旦跑完函数就自爆了,根本没法复用!那咋整?

别慌!咱有妙招:让每个线程启动后,不是干一票就跑,而是蹲在调度函数里死循环——有活就干,没活就睡,直到线程池喊“收工”才滚蛋!这不就实现线程复用了嘛?简直比小区门口的共享单车还环保!

整个线程池就俩核心积木:

  • 任务队列(Task Queue):谁先来谁先干,FIFO安排得明明白白。
  • 线程大军(Thread Pool):一群打工人,24小时待命,随时准备搬砖。

而这俩之间的配合,妥妥一个生产者-消费者模型——主线程往队列里塞任务(生产者),工作线程从队列里抢任务(消费者)。为了防止大家抢成一团乱麻,咱祭出两大法宝:

  • 一把mutex锁:保证同一时间只有一个人能动队列;
  • 一个条件变量(condition_variable):队列空了?别傻等,直接睡觉!等有新任务来了,我“啪”一下把你叫醒!

积木1:SafeQueue —— 线程安全的任务队列

你以为直接拿个std::queue就能上战场?Too young!多线程环境下,它脆得跟薯片一样。所以咱得给它套上盔甲——封装成SafeQueue!

原理贼简单:所有操作都先抢锁,抢到才能动队列。你看这代码,enqueue、dequeue、empty、size,哪个不是裹着unique_lock出场?就像你去ATM取钱,门一关,别人只能干瞪眼!

// thread_pool.h 里的 SafeQueue 实现 template <typename T> class SafeQueue { private: std::queue<T> m_queue; // 利用模板函数构造队列 std::mutex m_mutex; // 访问互斥信号量 public: SafeQueue() {} SafeQueue(SafeQueue &&other) {} ~SafeQueue() {} bool empty() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.empty(); } int size() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.size(); } // 队列添加元素 void enqueue(T &t) { std::unique_lock<std::mutex> lock(m_mutex); m_queue.emplace(t); // 安全入队,稳如老狗 } // 队列取出元素 bool dequeue(T &t) { std::unique_lock<std::mutex> lock(m_mutex); if (m_queue.empty()) return false; t = std::move(m_queue.front()); // 右值引用,性能拉满 m_queue.pop(); return true; } };

看明白没?加锁 → 操作 → 自动解锁,三步走,安全又高效!这SafeQueue就是咱线程池的“任务快递柜”,谁来取件都得排队刷脸!


积木2:ThreadPool —— 真正的“包工头”

提交任务?submit()给你安排得明明白白!

你想往线程池扔个lambda?成员函数?普通函数?带参数?带返回值?统统接得住!秘诀就是C++11的语法糖三件套

  • 可变模板参数(Args...):参数多少都不怕;
  • 尾置返回类型(-> decltype(...)):自动推导返回值类型,再也不用手动写typename std::result_of<...>::type那种绕死人的玩意儿;
  • 完美转发(std::forward):左值右值原样传递,绝不掉帧!

重点来了!为了让任务能异步执行并拿到结果,咱用std::packaged_task把函数打包,再用shared_ptr包一层(方便复制),最后塞进一个void()的lambda里丢进队列。为啥要转成void()?因为任务队列只认统一接口!就像快递柜,不管你寄的是手机还是泡面,都得装进标准箱子里!

// Submit a function to be executed asynchronously by the pool template <typename F, typename... Args> auto submit(F &&f, Args &&...args) -> std::future<decltype(f(args...))> { // 1. 绑定函数和参数 → func() std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); // 2. 打包成packaged_task(带返回值的异步任务) auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func); // 3. 包装成void()函数塞进队列 std::function<void()> warpper_func = [task_ptr]() { (*task_ptr)(); }; // 4. 队列通用安全封包函数,并压入安全队列 m_queue.enqueue(warpper_func); // 5. 唤醒一个等待中的线程 m_conditional_lock.notify_one(); // 6. 返回future,让你随时能查结果 return task_ptr->get_future(); }

此时你看到decltype(f(args...))脑壳是不是嗡嗡的。别慌!这其实就是告诉编译器:“兄弟,你先看看f(args...)返回啥类型,我就按那个类型返回std::future!”——智能推导,省心省力


内置打工人:ThreadWorker

每个线程启动后,就化身ThreadWorker,死循环干活:

  • 检查线程池是否shutdown?没关就继续;
  • 加锁,看队列有没有活?没有就wait()睡觉;
  • 有活?赶紧dequeue出来,解锁,然后猛干!
class ThreadWorker // 内置线程工作类 { private: int m_id; // 工作id ThreadPool *m_pool; // 所属线程池 public: ThreadWorker(ThreadPool *pool, const int id) : m_pool(pool), m_id(id) {} void operator()() { std::function<void()> func; bool dequeued; while (!m_pool->m_shutdown) { { std::unique_lock<std::mutex> lock(m_pool->m_conditional_mutex); // 队列空了?睡觉! if (m_pool->m_queue.empty()) { m_pool->m_conditional_lock.wait(lock); } // 抢单! dequeued = m_pool->m_queue.dequeue(func); } if (dequeued) func(); // 干活! } } };

注意那个wait(lock)——它会在等待时自动释放锁,等被唤醒后再重新加锁。这设计简直绝了,既省CPU又防死锁,堪称线程界的“节能睡眠模式”!


完整线程池代码(直接复制就能跑!)

来!上主菜!下面这份代码,就是咱亲手搓出来的C++11线程池全家桶,包含所有细节,连构造函数删除都给你安排明白了(防拷贝,防移动,专治各种手滑):

// thread_pool.h #ifndef THREAD_POOL_H #define THREAD_POOL_H #include <mutex> #include <queue> #include <functional> #include <future> #include <thread> #include <utility> #include <vector> template <typename T> class SafeQueue { private: std::queue<T> m_queue; std::mutex m_mutex; public: SafeQueue() {} SafeQueue(SafeQueue &&other) {} ~SafeQueue() {} bool empty() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.empty(); } int size() { std::unique_lock<std::mutex> lock(m_mutex); return m_queue.size(); } void enqueue(T &t) { std::unique_lock<std::mutex> lock(m_mutex); m_queue.emplace(t); } bool dequeue(T &t) { std::unique_lock<std::mutex> lock(m_mutex); if (m_queue.empty()) return false; t = std::move(m_queue.front()); m_queue.pop(); return true; } }; class ThreadPool { private: class ThreadWorker { private: int m_id; ThreadPool *m_pool; public: ThreadWorker(ThreadPool *pool, const int id) : m_pool(pool), m_id(id) {} void operator()() { std::function<void()> func; bool dequeued; while (!m_pool->m_shutdown) { { std::unique_lock<std::mutex> lock(m_pool->m_conditional_mutex); if (m_pool->m_queue.empty()) { m_pool->m_conditional_lock.wait(lock); } dequeued = m_pool->m_queue.dequeue(func); } if (dequeued) func(); } } }; bool m_shutdown; SafeQueue<std::function<void()>> m_queue; std::vector<std::thread> m_threads; std::mutex m_conditional_mutex; std::condition_variable m_conditional_lock; public: ThreadPool(const int n_threads = 4) : m_threads(std::vector<std::thread>(n_threads)), m_shutdown(false) {} ThreadPool(const ThreadPool &) = delete; ThreadPool(ThreadPool &&) = delete; ThreadPool &operator=(const ThreadPool &) = delete; ThreadPool &operator=(ThreadPool &&) = delete; void init() { for (int i = 0; i < m_threads.size(); ++i) { m_threads.at(i) = std::thread(ThreadWorker(this, i)); } } void shutdown() { m_shutdown = true; m_conditional_lock.notify_all(); // 全员下班! for (int i = 0; i < m_threads.size(); ++i) { if (m_threads.at(i).joinable()) { m_threads.at(i).join(); } } } template <typename F, typename... Args> auto submit(F &&f, Args &&...args) -> std::future<decltype(f(args...))> { std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func); std::function<void()> warpper_func = [task_ptr]() { (*task_ptr)(); }; m_queue.enqueue(warpper_func); m_conditional_lock.notify_one(); return task_ptr->get_future(); } }; #endif

测试样例:让线程池动起来!

光说不练假把式!下面这段测试代码,直接验证咱的线程池能不能扛住高并发!

// test.cpp #include <iostream> #include <random> #include "thread_pool.h" std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution<int> dist(-1000, 1000); auto rnd = std::bind(dist, mt); void simulate_hard_computation() { std::this_thread::sleep_for(std::chrono::milliseconds(2000 + rnd())); } void multiply(const int a, const int b) { simulate_hard_computation(); std::cout << a << " * " << b << " = " << a * b << std::endl; } void multiply_output(int &out, const int a, const int b) { simulate_hard_computation(); out = a * b; std::cout << a << " * " << b << " = " << out << std::endl; } int multiply_return(const int a, const int b) { simulate_hard_computation(); return a * b; } void example() { ThreadPool pool(3); // 3个打工人 pool.init(); // 开工! // 提交30个乘法任务 for (int i = 1; i <= 3; ++i) for (int j = 1; j <= 10; ++j) pool.submit(multiply, i, j); // 带输出参数的任务 int output_ref; auto future1 = pool.submit(multiply_output, std::ref(output_ref), 5, 6); future1.get(); // 等结果 std::cout << "Last result (by ref): " << output_ref << std::endl; // 带返回值的任务 auto future2 = pool.submit(multiply_return, 5, 3); int res = future2.get(); std::cout << "Last result (by return): " << res << std::endl; pool.shutdown(); // 收工! } int main() { example(); return 0; }

跑起来你会发现:30个乘法任务并发执行,输出顺序乱七八糟(正常!),但每个结果都对!还能通过future.get()同步拿结果——异步提交 + 同步获取,灵活得一批

深耕 C/C++ 开发的小伙伴,如果你不知选择哪个方向就业发展,纠结 C++ 的学习价值与行业前景,不妨看为什么很多人劝退学 C++,但大厂核心岗位还是要 C++?,帮你理清行业逻辑,找准发展方向;

如果你想深耕Linux C/C++后端,却没有清晰的学习路径,【大厂标准】Linux C/C++ 后端进阶学习路线给你大厂级的进阶指南,少走弯路;

如果你想入局音视频流媒体领域,零基础不知从何下手,音视频流媒体高级开发 - 学习路线精准拆解学习重点,搭建完整知识体系;

如果你瞄准桌面开发/嵌入式开发,想掌握 C++ Qt 核心技能,C++ Qt 学习路线一条龙!(桌面开发 & 嵌入式开发)一站式教你从入门到实战;

如果你想挑战Linux内核开发,追求硬核技术提升,Linux 内核学习指南,硬核修炼手册带你吃透内核底层,突破技术瓶颈;

如果你正备战面试,急需刷题提分,C/C++ 高频八股文面试题 1000 题(三)直击面试高频考点,帮你快速查漏补缺;

如果你面试总被手撕代码难住,尤其是线程池相关考点,手撕线程池:C++ 程序员的能力试金石手把手教你实现核心逻辑,夯实编程功底;

如果你着急找工作、无面试机会、拿不到offer,分不清是技术欠缺还是简历问题,更要从这些文章里找准问题核心,针对性提升,快速突破求职瓶颈!


总结

线程池这东西说白了就是管理任务和线程的配对,掌握核心原理,剩下的都是语法糖

这套线程池虽然只有百来行,但五脏俱全!支持任意函数、任意参数、返回值获取、线程安全、资源自动回收……作为C++并发入门真的很合适!

不过老铁们注意啊:实际项目中还得考虑异常安全、任务拒绝策略、动态扩缩容啥的。但作为基础框架,它已经把核心思想给你焊死了——复用线程 + 安全队列 + 条件唤醒,三大心法,缺一不可!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 8:03:04

打工人狂喜向量引擎让Claude4.6和GPT5跑得比领导催需求还快

前言 最近AI圈又炸了 OpenAI的Claw刚发布就被玩坏 各路大神都在测试极限 但你知道吗 真正让这些AI模型跑得飞快的秘密 不是算力 不是显卡 而是一个你可能从没听说过的东西 向量引擎 今天我就来给大家掰扯掰扯这个神器 保证让你看完就能上手什么是向量引擎 先说个大白话 你有没有…

作者头像 李华
网站建设 2026/6/10 7:57:32

2026必备!9个降AIGC工具测评:本科生降AI率必看

随着AI技术在学术领域的广泛应用&#xff0c;越来越多的本科生开始面临论文中AIGC率过高的问题。如何在保持原意不变的前提下&#xff0c;有效降低AI痕迹和查重率&#xff0c;成为许多学生关注的焦点。AI降重工具应运而生&#xff0c;它们通过智能算法对文本进行深度优化&#…

作者头像 李华
网站建设 2026/6/10 7:51:21

AI写专著新突破!工具功能详解,帮你打造高质量学术专著

对许多研究人员来说&#xff0c;撰写学术专著最大的挑战就是“有限的时间”与“无限的需求”之间的矛盾。专著的写作通常需要三到五年&#xff0c;甚至更长的时间&#xff0c;而研究者还需兼顾教学、科研项目和学术交流等多种任务&#xff0c;能用于写作的时间往往是支离破碎的…

作者头像 李华
网站建设 2026/6/10 8:00:11

聚焦算法:深入解析NVIDIA CUDA Tile硬件抽象技术

随着自2006年NVIDIA CUDA平台发明以来最大的一次进步&#xff0c;CUDA 13.1推出了NVIDIA CUDA Tile。这项激动人心的创新引入了一个用于基于图块的并行编程的虚拟指令集&#xff0c;其核心在于能够以更高的层次编写算法&#xff0c;并抽象掉专用硬件&#xff08;如张量核心&…

作者头像 李华
网站建设 2026/6/10 8:03:48

AI写论文不用愁!这4款AI论文生成利器,快速提升写作效率!

你是否也在为撰写期刊论文而烦恼&#xff1f;面对数量庞大的文献、各种格式要求和不断的修改&#xff0c;学术工作往往让人感到疲惫不堪。别担心&#xff0c;现在有了AI论文写作工具&#xff0c;不仅能帮你节省时间&#xff0c;还能提高效率。接下来&#xff0c;本文将介绍四款…

作者头像 李华