C++20线程池

本实现基于 C++20 标准,摒弃了传统的常规标志位与易错组件。以下是核心代码段的设计动机与原理:

为什么使用 std::jthread 而不是 std::thread?

std::jthread 提供了自动汇合和协作式中断机制。它的析构函数会自动发出停止请求并阻塞等待线程结束,彻底消除了传统 std::thread 忘记调用 join() 导致程序崩溃的隐患,并且代码更简洁,安全性更高。

这看着像GC机制,自动销毁往往伴随着一些代价,虽然代价很小,但在性能要求极高的场景(极低延迟与高频交易系统,硬实时系统,高频短生命周期并发任务)上面会体现出来。

为什么不需要自定义 bool stop 标志位?

工作线程直接接收 std::stop_token。析构函数中遍历调用 request_stop()(或者直接依赖 jthread 的自动机制),st.stop_requested() 会安全地接管停止信号。这使得中断逻辑与操作系统底层的线程机制绑定更深,代码更简洁。

为什么条件变量等待的判断逻辑是 st.stop_requested() || !tasks.empty(),且随后判断 st.stop_requested() && tasks.empty()?

这是为了实现优雅降级。收到停止信号后,线程不会立刻暴力退出,而是会继续消耗队列中残留的任务。只有当既收到停止请求,且任务队列被彻底清空时,线程才会 return 结束。

为什么必须用 std::shared_ptr 包装 std::packaged_task?

这是 C++ 线程池最经典的坑。任务队列的类型是 std::function<void()>,C++ 标准要求 std::function 内部持有的对象必须是可拷贝的(CopyConstructible)。但 std::packaged_task 作为一个管理异步状态的类,是仅可移动的(Move-Only)。通过 std::make_shared 将其放到堆上,Lambda 表达式按值捕获这个 shared_ptr(发生拷贝),从而完美满足了 std::function 的编译要求。

为什么使用 requires std::invocable<Func, Args...>?

利用 C++20 的 Concepts 机制进行编译期约束。如果调用者传入了参数类型不匹配的函数,编译器会在 enqueue 调用处直接报错,而不是在深层模板实例化时抛出几百行的乱码错误,极大提升了 API 的易用性。

为什么 Lambda 捕获要写成 f=std::forward\(f), ...args=std::forward\(args)...() mutable?

这是 C++20 引入的带初始化器的包扩展捕获(Pack expansion in lambda init-capture)。它结合了完美转发,确保任务入队时,参数的左值/右值属性被完整保留到实际执行的那一刻。mutable 关键字则是因为 std::invoke 在执行某些带有内部状态的仿函数时,可能需要修改捕获进来的按值变量。

为什么入队时要判断 pool0.get_stop_token().stop_requested()?

防止"死后入队"。当线程池生命周期即将结束(析构函数已触发)时,如果其他线程还在尝试往池子里塞任务,这些任务将永远不会被执行。直接抛出 std::runtime_error 可以在运行时尽早暴露时序 Bug。

cpp 复制代码
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <functional>
#include <vector>
#include <queue>
#include<future>
#include<memory>
#include <concepts>
#include<stdexcept>
class ThreadPool
{
public:
    explicit ThreadPool(size_t n = std::thread::hardware_concurrency()) 
    {
        pool.reserve(n);
        for(size_t i=0;i<n;++i){
            pool.emplace_back([this](std::stop_token st){
                while(!st.stop_requested()){
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(mu);
                        c_v.wait(lock,[this,&st](){return st.stop_requested()||!tasks.empty();});
                        if(st.stop_requested()&&tasks.empty())return;
                        task=std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }
    ~ThreadPool()
    {
        for (auto& t : pool) {
            t.request_stop();
        }
        c_v.notify_all();
    }
    template<typename Func,typename ...Args>
    requires std::invocable<Func,Args...> auto enqueue(Func&& f,Args&& ...args)->std::future<std::invoke_result_t<Func,Args...>>{
        using retType=std::invoke_result_t<Func,Args...>;
        auto task=std::make_shared<std::packaged_task<(retType())>>
        ([f=std::forward<Func>(f),...args=std::forward<Args>(args)...]()mutable{
            return std::invoke(f,std::move(args)...);});

        std::future<retType> res=task->get_future();
        {
            std::unique_lock<std::mutex>lock(mu);
            if (!pool.empty() && pool[0].get_stop_token().stop_requested()) {
                throw std::runtime_error("enqueue on stopped ThreadPool");
            }
            tasks.emplace([task](){(*task)();});
        }
        c_v.notify_one();
        return res;
    }
private:
    std::mutex mu;
    std::condition_variable c_v;
    std::vector<std::jthread> pool;
    std::queue<std::function<void()>> tasks;
};
相关推荐
汉克老师1 小时前
GESP2026年9月认证C++一级( 第三部分编程题(2、棋盘上的奖赏))精讲
c++·gesp·小学生·学c++编程
努力努力再努力wz1 小时前
【CUDA 入门系列】核函数与线程模型:一文理清 Grid、Block、dim3、内置变量与全局索引
c++·架构
ShineWinsu1 小时前
对于Redis:ZSet类型的解析
数据库·c++·redis·缓存·面试·有序集合·zset
奋发向前wcx1 小时前
CSP-J复赛模拟赛2 王晨旭补题 2026.10.3
算法
All for pursuit.2 小时前
【回溯-5】78.子集
数据结构·c++·算法·leetcode
辛苦才能2 小时前
C++ 红黑树(上):五条规则背后的近似平衡与插入的三类情况
开发语言·c++
蒲公英eric2 小时前
Python工具链与LaTeX排版:把兵器配齐
人工智能·python·算法·数学建模·数学建模工具
by209992 小时前
学会使用 std::string 类,并理解其内部是如何管理字符串的详细阐述(下)
c++·经验分享·笔记·字符串·类和对象·string
.道阻且长.2 小时前
C++11 :新的类功能,lambda,包装器
开发语言·c++·后端