首页 游戏 软件 资讯 排行榜 专题
首页
编程语言
从零实现一个轻量级C++线程池

从零实现一个轻量级C++线程池

热心网友
14
转载
2026-05-05

一、引言

本文将带领你从零开始,动手实现一个轻量级、高性能且易于扩展的C++线程池。这不仅是一次深入理解多线程编程核心机制的绝佳实践,更能为你的C++项目带来显著的并发性能提升。

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

整个实现过程将系统性地运用以下现代C++并发编程关键技术:

  1. std::thread:实现工作线程的创建与生命周期管理。
  2. std::mutex / std::unique_lock:构建线程安全的共享数据保护机制,杜绝数据竞争。
  3. std::condition_variable:实现高效的线程间同步与任务等待通知模型。
  4. std::function / std::future / std::packaged_task / std::bind:构建灵活通用的任务封装、提交与异步结果获取体系。

二、什么是线程池

线程池是一种经典的并发设计模式,其核心思想在于线程的预创建与复用。它预先初始化一组工作线程并置于“池”中待命。当有任务需要异步执行时,无需临时创建线程,而是直接从池中分配一个空闲线程来执行任务,任务完成后线程回归池中等待下一次分配。

从零实现一个轻量级C++线程池

三、为什么需要线程池

频繁地动态创建和销毁线程会带来显著的系统开销,包括内核对象创建、上下文切换和内存分配等。C++线程池通过复用线程资源,有效解决了这一问题,并带来三大核心优势:

  • 显著降低系统开销:线程复用避免了反复创建/销毁的成本,提升了整体资源利用率和程序性能。
  • 提升任务响应速度:任务提交后立即可被池中空闲线程执行,省去了线程创建的时间延迟,尤其适合高并发、短任务的场景。
  • 增强线程的可控性与可管理性:线程作为系统稀缺资源,无限制创建易导致内存耗尽。线程池提供了统一的入口,允许开发者精确控制并发线程数量,便于进行性能调优、监控和资源隔离。

四、线程池的核心组成

一个健壮的C++线程池实现通常包含以下四个核心组件:

  1. 工作线程集合 (Worker Threads):线程池预先创建并维护的一组线程。它们持续运行,核心职责是从任务队列中获取并执行任务。
  2. 线程安全的任务队列 (Task Queue):作为生产者(任务提交者)与消费者(工作线程)之间的缓冲区。所有待处理的任务在此排队,必须保证其入队、出队操作的原子性与线程安全性。
  3. 同步与通信机制 (Synchronization)
    • 互斥锁 (Mutex):保护任务队列等共享数据结构,确保多线程并发访问时的数据一致性。
    • 条件变量 (Condition Variable):实现高效的任务等待/通知机制。当队列为空时,工作线程在此阻塞休眠;当新任务到达时,通知唤醒等待的线程,避免了CPU空转的忙等待(busy-waiting)。
  4. 通用任务提交接口 (Task Submission Interface):对外提供统一的任务提交API。它需要高度灵活,能够接收函数指针、成员函数、Lambda表达式、仿函数等任何可调用对象,并支持参数绑定与返回值获取。

五、C++线程池的完整实现与解析

在深入代码实现前,我们先简要回顾几个关键的C++标准库组件,它们是构建线程池的基石。

std::condition_variable(条件变量):线程同步的“信号灯”。工作线程在任务队列为空时,通过其wait()方法释放锁并进入阻塞状态。当submit()提交新任务后,调用notify_one()notify_all()唤醒等待的线程。这彻底消除了轮询带来的CPU资源浪费。

void wait (unique_lock& lck, Predicate pred);

该函数接受一个已锁定的互斥锁和一个谓词(Predicate)。内部会先释放锁,使线程阻塞,避免持有锁休眠。线程被唤醒后,会重新获取锁并检查谓词条件,只有谓词返回true才会真正继续执行,防止虚假唤醒。

void notify_one() noexcept;

随机唤醒一个在该条件变量上等待的线程。

void notify_all() noexcept;

唤醒所有在该条件变量上等待的线程。

std::future:用于获取异步任务结果的工具。它封装了异步操作的返回值或异常,提供了get()方法以同步等待方式获取结果,简化了线程间数据传递的复杂度。

T get();

阻塞调用线程,直至关联的异步任务完成,并返回其结果或抛出存储的异常。

std::function:通用的多态函数包装器。它将各种可调用实体(函数指针、Lambda、bind表达式、仿函数等)统一为特定签名的可调用对象,是构建通用任务队列类型的关键。

std::packaged_task:高级任务包装器。它将一个可调用对象与其异步结果(std::future)关联。通过get_future()获取future,执行任务后结果自动存入future,完美解决了任务返回值传递问题。

std::bind:通用参数绑定器。它可以将可调用对象与其部分或全部参数进行绑定,生成一个新的可调用实体,用于适配固定参数的任务提交接口。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class ThreadPool {
public:
    ThreadPool(size_t thread_num = 4):
        _thread_num(thread_num),
        _start(false),
        _stop(false)
    {}
    ~ThreadPool(){
        if (_start && !_stop) stop();
    }
    ThreadPool(const ThreadPool&) = delete;
    ThreadPool& operator=(const ThreadPool&) = delete;
    ThreadPool(ThreadPool&&) = delete;
    ThreadPool& operator=(ThreadPool&&) = delete;

    void start() {
        std::unique_lock lock(_mutex);
        if (_start) return;
        _workers.reserve(_thread_num);
        for (size_t i = 0; i < _thread_num; i++) {
            _workers.emplace_back(std::thread([this](){
                work_loop();
            }));
        }
        _start = true;
    }

    void stop() {
        {
            std::unique_lock lock(_mutex);
            if (!_start || _stop) return;
            // 在join回收线程之前,必须先将_stop置为true,
            // 否则工作线程可能会一直阻塞在条件变量上,导致无法正常退出,甚至会导致程序崩溃
            _stop = true;
        }
        _cond.notify_all();
        for (auto& worker : _workers) {
            if (worker.joinable()) worker.join();
        }
    }

    template
    auto submit(F&& f, Args&&... args)->std::future> {
        using return_type = std::invoke_result_t;
        // 绑定函数参数,并交给任务包装器
        auto task = std::make_shared>(
            std::bind(std::forward(f), std::forward(args)...)
        );
        // 获取关联的future
        std::future res = task->get_future();
        // 加锁+入队列
        {
            std::unique_lock lock(_mutex);
            if (_stop || !_start) throw std::runtime_error("线程池未启动!");
            _tasks.emplace([task](){(*task)();});
        }
        _cond.notify_one();
        return res;
    }

private:
    void work_loop() {
        while (true) {
            std::function task;
            {
                std::unique_lock lock(_mutex);
                _cond.wait(lock, [this](){
                    return _stop || !_tasks.empty();
                });
                if (_stop && _tasks.empty()) return;
                task = std::move(_tasks.front());
                _tasks.pop();
            }
            task();
        }
    }

private:
    std::vector _workers;
    std::queue> _tasks;
    std::mutex _mutex;
    std::condition_variable _cond;
    size_t _thread_num;
    bool _start;
    bool _stop;
};

int add(int a, int b) {
    return a + b;
}

void print() {
    std::cout << "-------------------print-------------------" << std::endl;
    std::cout << "Hello World!" << std::endl;
}

int main() {
    ThreadPool pool(4);
    pool.start();
    std::cout << "==================ThreadPoolTest==================" << std::endl;
    pool.submit([](){
        std::cout << "-------------------lambda-------------------" << std::endl;
        std::cout << "this is a lambda!" << std::endl;
    });
    std::this_thread::sleep_for(std::chrono::seconds(3));  
    auto ret1 = pool.submit(add, 10, 20);
    std::cout << "-------------------add-------------------" << std::endl;
    std::cout << "10 + 20 = " << ret1.get() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));  
    pool.submit(print);
    pool.stop();
    return 0;
}

运行结果:

从零实现一个轻量级C++线程池

本实现代码采用了C++17标准(使用了std::invoke_result_t等特性)。

一个关键设计在于:任务队列存储的是std::function类型,即无参数、无返回值的任务。那么,如何支持带参数和返回值的函数呢?这依赖于以下三步精妙的适配组合:

  1. 使用std::bind进行参数绑定:将目标函数与其实参预先绑定,生成一个符合return_type()签名的可调用对象。
  2. 使用std::packaged_task封装返回值:将上一步生成的可调用对象用packaged_task包装,其返回值会自动存入内部的std::future中。
  3. 使用Lambda进行最终类型擦除:通过一个捕获packaged_task智能指针的Lambda,调用其operator(),最终生成一个符合void()签名的任务,放入队列。

这套组合拳实现了强大的泛化能力,无论原函数签名如何,都能统一适配到线程池的任务队列中。

此外,代码中使用了std::forward进行完美转发,这确保了在submit模板函数中,传入的函数对象F和参数包Args...能够保持其原有的左值或右值引用类型,从而触发移动语义,避免不必要的拷贝,提升性能。

至此,一个具备生产级雏形的轻量级C++线程池就完整实现了。通过这个实践,你不仅能掌握线程池的核心原理,还能深入理解现代C++并发编程工具链的协同工作方式,为开发高性能并发应用打下坚实基础。

来源:https://www.jb51.net/program/36227121a.htm
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

红米Note11 Pro更新系统需连WiFi吗?
电脑教程
红米Note11 Pro更新系统需连WiFi吗?

红米Note 11 Pro系统升级,为何坚持要求连接Wi-Fi? 当红米Note 11 Pro收到MIUI或澎湃OS的系统更新推送时,官方总会明确提示:整个过程请在Wi-Fi网络环境下完成。这项要求并非随意设定,而是基于清晰的技术与体验考量。一次完整的系统升级包,其大小通常在2GB至4GB之间。如果

热心网友
05.05
小米13ultra有nfc功能吗
电脑教程
小米13ultra有nfc功能吗

小米13 Ultra的NFC功能深度解析:它如何重新定义“全场景智能交互”? 在旗舰手机领域,NFC功能看似已成为标配,但体验却千差万别。小米13 Ultra所搭载的全功能NFC方案,在“全能”与“好用”两个维度上树立了新的标杆。它不仅无缝集成了公交卡模拟、门禁卡复制、数字车钥匙等核心生活服务,更全

热心网友
05.05
嵌入式消毒柜电源插座位置必须外露吗?
电脑教程
嵌入式消毒柜电源插座位置必须外露吗?

嵌入式消毒柜电源插座安装指南:隐蔽式布局提升安全与美观 在规划嵌入式消毒柜的安装方案时,电源插座的布局方式直接影响到最终的整体效果与安全性。正确的做法是避免插座外露,采用隐蔽式安装。根据国家《住宅厨房设计规范》及主流厨电品牌的安装标准,推荐将插座预留在消毒柜后方或侧方的墙体内部,安装高度宜控制在距地

热心网友
05.05
魔音耳机操作说明包含充电指示吗?
电脑教程
魔音耳机操作说明包含充电指示吗?

是的,魔音(Beats)耳机充电状态一目了然,指示灯明确显示 当你为Beats头戴式耳机充电时,如何判断它是否已经充满?答案就藏在机身自带的五段式LED电量指示灯里。在充电过程中,这排指示灯会持续闪烁,实时反馈充电进度。一旦所有五个指示灯全部转为稳定常亮、不再闪烁,即代表电池已完全充满。整个充电周期

热心网友
05.05
博朗剃须刀如何识别型号?
电脑教程
博朗剃须刀如何识别型号?

博朗剃须刀型号全解析:从编码规则到选购技巧的终极指南 面对博朗剃须刀复杂的字母数字组合感到困惑?实际上,其型号命名体系逻辑严谨,是用户选购的核心依据。简单来说,型号首位的数字(1、3、5、7、9)直接代表产品系列,数字越大,通常意味着技术越先进、功能越全面、定位越高端。例如,顶级的9系旗舰机型普遍搭

热心网友
05.05