3.1.1更现代化的线程池

CaliInn 2026-01-27 19:00:13

先贴上更新后的线程池全代码:

#pragma once
#include <functional>
#include <queue>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <iostream>

template <typename T>
class BlockingQueue
{
public:
    // 默认构造,显式定义析构
    explicit BlockingQueue(bool nonblock = false) : nonblock_(nonblock) {}
    ~BlockingQueue() = default;

    // 禁用拷贝,防止多个实例共享同一个锁引发崩溃
    BlockingQueue(const BlockingQueue &) = delete;
    BlockingQueue &operator=(const BlockingQueue &) = delete;

    // 使用右值引用,避免 std::function 的昂贵拷贝
    void Push(T &&value)
    {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            queue_.push(std::move(value));
        }
        not_empty_.notify_one();
    }

    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(mutex_);
        // wait 会在 nonblock_ 为 true 或队列不为空时解除阻塞
        not_empty_.wait(lock, [this]
                        { return !queue_.empty() || nonblock_; });

        if (queue_.empty())
            return false;

        value = std::move(queue_.front());
        queue_.pop();
        return true;
    }

    void Cancel()
    {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            nonblock_ = true;
        }
        not_empty_.notify_all(); // 唤醒所有等待中的线程准备退出
    }

private:
    bool nonblock_;
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable not_empty_;
};

template <typename T>
class BlockingQueuePro
{
public:
    explicit BlockingQueuePro(bool nonblock = false) : nonblock_(nonblock) {}
    ~BlockingQueuePro() = default;

    BlockingQueuePro(const BlockingQueuePro &) = delete;
    BlockingQueuePro &operator=(const BlockingQueuePro &) = delete;

    void Push(T &&value)
    {
        {
            std::lock_guard<std::mutex> lock(prod_mutex_);
            prod_queue_.push(std::move(value));
        }
        not_empty_.notify_one();
    }

    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(cons_mutex_);
        if (cons_queue_.empty() && SwapQueue_())
        {
            return false;
        }
        value = std::move(cons_queue_.front());
        cons_queue_.pop();
        return true;
    }

    void Cancel()
    {
        {
            std::lock_guard<std::mutex> lock(cons_mutex_);
            nonblock_ = true;
        }
        not_empty_.notify_all();
    }

private:
    bool nonblock_;
    std::queue<T> prod_queue_;
    std::queue<T> cons_queue_;
    std::mutex prod_mutex_;
    std::mutex cons_mutex_;
    std::condition_variable not_empty_;

    int SwapQueue_()
    {
        std::unique_lock<std::mutex> lock(prod_mutex_);
        not_empty_.wait(lock, [this]
                        { return !prod_queue_.empty() || nonblock_; });
        std::swap(prod_queue_, cons_queue_);
        return cons_queue_.size();
    }
};

template <typename T>
class BlockingQueuePro2
{
public:
    explicit BlockingQueuePro2(bool nonblock = false) : nonblock_(nonblock) {}

    void Push(T &&value)
    {
        {
            std::lock_guard<std::mutex> lock(prod_mutex_);
            prod_queue_.push(std::move(value));
        }
        not_empty_.notify_one(); // 唤醒正在 wait 的消费者
    }

    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(cons_mutex_);

        // 如果消费缓冲区空了,尝试去生产缓冲区交换数据
        if (cons_queue_.empty())
        {
            std::unique_lock<std::mutex> p_lock(prod_mutex_);
            // 等待直到:1. 生产队列有东西 2. 队列被取消
            not_empty_.wait(p_lock, [this]
                            { return !prod_queue_.empty() || nonblock_; });

            if (prod_queue_.empty() && nonblock_)
            {
                return false; // 真正退出条件
            }

            // 交换缓冲区,尽量缩短持有 prod_mutex_ 的时间
            std::swap(prod_queue_, cons_queue_);
        }

        // 此时 cons_queue_ 肯定不为空(除非被 Cancel)
        if (cons_queue_.empty())
            return false;

        value = std::move(cons_queue_.front());
        cons_queue_.pop();
        return true;
    }

    void Cancel()
    {
        {
            // 修改状态时,最好把两个锁都拿到,或者至少拿生产者锁(因为 wait 在那里)
            std::lock_guard<std::mutex> lock1(prod_mutex_);
            std::lock_guard<std::mutex> lock2(cons_mutex_);
            nonblock_ = true;
        }
        not_empty_.notify_all();
    }

private:
    bool nonblock_;
    std::queue<T> prod_queue_;
    std::queue<T> cons_queue_;
    std::mutex prod_mutex_;
    std::mutex cons_mutex_;
    std::condition_variable not_empty_;
};

template <typename T>
class myBlockingQueue2
{
private:
    bool nonblock_;
    std::queue<T> prod_queue_;
    std::queue<T> cons_queue_;
    std::mutex cons_mutex_;
    std::mutex prod_mutex_;
    std::condition_variable not_empty_;

public:
    void Push(T &&value)
    {
        {
            std::lock_guard<std::mutex> lock(prod_mutex_);
            prod_queue_.push(std::move(value))
        }
        not_empty_.notify_one();
    }

    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(cons_queue_);
        if (cons_queue_.empty())
        {
            std::unique_lock<std::mutex> p_lock(prod_mutex_);
            not_empty_.wait(p_lock, [this]
                            { return !prod_queue_.empty() || nonblock_; });
            if (prod_queue_.empty() && nonblock_)
            {
                return false;
            }
            std::swap(prod_queue_, cons_queue_);
        }
        if (cons_queue_.empty())
        {
            return false
        }
        value = std::move(cons_queue_.front());
        cons_queue_.pop();
        return true;
    }

    void Cancel()
    {
        {
            std::lock_guard<std::mutex> lock1(prod_mutex_);
            std::lock_guard<std::mutex> lock2(cons_mutex_);
            nonblock_ = true;
        }
        not_empty_.notify_all();
    }
    explicit myBlockingQueue2(bool nonblock = false) : nonblock_(nonblock) {};
    ~myBlockingQueue2() = default;
};

class ThreadPool
{
public:
    explicit ThreadPool(size_t threads_num)
    {
        for (size_t i = 0; i < threads_num; ++i)
        {
            // 使用 emplace_back 直接构造线程
            workers_.emplace_back([this]
                                  { Worker(); });
        }
    }

    // 析构顺序:1. 停止队列 2. 等待线程结束
    ~ThreadPool()
    {
        task_queue_.Cancel();
        for (auto &worker : workers_)
        {
            if (worker.joinable())
            {
                worker.join();
            }
        }
    }

    // 禁用拷贝构造
    ThreadPool(const ThreadPool &) = delete;
    ThreadPool &operator=(const ThreadPool &) = delete;

    // 接受右值,提高效率
    void Post(std::function<void()> task)
    {
        task_queue_.Push(std::move(task));
    }

private:
    void Worker()
    {
        while (true)
        {
            std::function<void()> task;
            // 如果 Pop 返回 false,说明队列已关闭且任务处理完毕
            if (!task_queue_.Pop(task))
            {
                break;
            }

            // 增加异常处理,防止单个任务抛出异常导致整个线程池崩溃
            if (task)
            {
                try
                {
                    task();
                }
                catch (const std::exception &e)
                {
                    std::cerr << "Task exception: " << e.what() << std::endl;
                }
                catch (...)
                {
                    std::cerr << "Unknown task exception" << std::endl;
                }
            }
        }
    }

    BlockingQueue<std::function<void()>> task_queue_;
    std::vector<std::thread> workers_;
};

Push()为什么使用std:move

推荐阅读: 假设有一个 1KB 的大对象, std::move()能节省拷贝吗?,这个文章讲了std::move的原理,其本质是一个类型转换,将其转换为“右值”。右值的定义更像是数值本身,如我定义一个int a = 5,则5就是他的“右值”。当prod_queue_.push(std::move(value))发生时,std::movevalue强制转换为右值,然后调用push(T&& x)的实现。使用std::move后,变量的所有权将转移,生命周期也将被“掠夺”,比拷贝构造的效率更高。

explicit作用

避免隐式转换,如下面的代码,DoSomeThing将会创建一个大小为10的线程池,由于程序员对DoSomething的不熟悉导致他以为传入了一个int,结果却创建了一个线程池。 explicit限制这种行为不被允许。

void DoSomething(ThreadPool pool) { /* ... */ }

// 这种写法在逻辑上很怪,但编译器会允许:
DoSomething(10);

同样的,BlockingQueue(const BlockingQueue &) = delete;BlockingQueue &operator=(const BlockingQueue &) = delete;阻止了拷贝构造和赋值操作。

使用 Impl 结构体隐藏具体实现

直接在 ThreadPool.h 里只声明 struct Impl;。因为 unique_ptr<Impl> 只需要知道 Impl 是个结构体,至于 Impl 里面是否用了 BlockingQueue<T>,头文件根本不关心。

// ThreadPool.h 
class ThreadPool {
    // ... 公开接口 ...
private:
    struct Impl; // 只需要这一个前置声明
    std::unique_ptr<Impl> pimpl_;
};

// ---------------------------------

// ThreadPool.cpp
#include "BlockingQueue.h" // 模板的定义全在这里

struct ThreadPool::Impl {
    // 此时编译器已经看到了 BlockingQueue.h,所以能正常实例化
    BlockingQueue<std::function<void()>> task_queue_;
    // ... 其他成员 ...
};

为什么?

在 C++ 中,模板类的前置声明其实很麻烦

  1. 如果 BlockingQueue 以后增加了默认模板参数(例如 template <typename T, typename Container = std::deque<T>>),你必须在所有前置声明的地方同步修改。
  2. 方案二实现了真正的“物理隔离”:
  • ThreadPool.h:只负责接口。
  • ThreadPool.cpp:负责把 ThreadPoolBlockingQueuestd::thread 缝合在一起。

1. 什么是 Pimpl 模式?

Pimpl 的核心思想是:将类中所有的私有(private)成员变量和私有函数,全部封装到一个隐藏的结构体(或类)中,而在原本的头文件中只保留一个指向该结构体的智能指针。


2. 为什么要用它?

目前你的 ThreadPool.h 必须包含:

  • <vector>(存放线程)
  • <thread>
  • "BlockingQueue.h"(存放任务队列)

如果 BlockingQueue.h 里面又包含了大量的底层库,那么任何包含 ThreadPool.h 的地方,编译时间都会变长。使用 Pimpl 后,你的头文件会变得极其干净。


3. 重构ThreadPool

第一步:修改 ThreadPool.h

在头文件里,我们不再定义具体的成员变量,而是声明一个“实现类”。

#pragma once
#include <memory>
#include <functional>

// 1. 前置声明
class BlockingQueue; 

class ThreadPool {
public:
    explicit ThreadPool(size_t threads_num);
    ~ThreadPool(); // 注意:Pimpl 模式下,析构函数必须在 .cpp 里定义

    void Post(std::function<void()> task);

private:
    // 2. 定义一个隐藏的实现类(结构体)
    struct Impl;
    // 3. 唯一的私有成员:指向实现类的指针
    std::unique_ptr<Impl> pimpl_;
};

第二步:修改 ThreadPool.cpp

在这里,把原本属于 ThreadPool 的所有私有成员(包括 BlockingQueuevector<thread>)全部挪过来。

#include "ThreadPool.h"
#include "BlockingQueue.h" // 只有在这里才真正包含队列头文件
#include <vector>
#include <thread>

// 定义隐藏的实现类
struct ThreadPool::Impl {
    BlockingQueue<std::function<void()>> task_queue_;
    std::vector<std::thread> workers_;

    // 将原本的 Worker 函数也挪到这里
    void Worker(ThreadPool* parent) {
        while (true) {
            std::function<void()> task;
            if (!task_queue_.Pop(task)) break;
            if (task) { 
                try { task(); } catch (...) {} 
            }
        }
    }
};

ThreadPool::ThreadPool(size_t threads_num) 
    : pimpl_(std::make_unique<Impl>()) { // 创建实现类实例
    for (size_t i = 0; i < threads_num; ++i) {
        pimpl_->workers_.emplace_back([this] { pimpl_->Worker(this); });
    }
}

// 必须在 .cpp 里显式定义析构函数,因为编译器需要看到 Impl 的完整定义才能销毁 unique_ptr
ThreadPool::~ThreadPool() {
    pimpl_->task_queue_.Cancel();
    for (auto &worker : pimpl_->workers_) {
        if (worker.joinable()) worker.join();
    }
}

void ThreadPool::Post(std::function<void()> task) {
    pimpl_->task_queue_.Push(std::move(task));
}

4. 这样做解决了什么?

  1. 二进制兼容性(ABI 稳定):如果你以后想把 vector<thread> 改成别的东西,你只需要修改 .cpp 文件。使用 ThreadPool 的第三方代码完全不需要重新编译,因为 ThreadPool.h 的结构没变(永远只有一个指针)。
  2. 编译加速:即便 BlockingQueue.h 里引用了半个地球的库,它们也只会被编译一次(在 ThreadPool.cpp 中),而不会顺着头文件蔓延到整个项目。
  3. 接口纯粹:使用者在 IDE 的自动补全里,只会看到 Post 等公开接口,看不到你内部实现的私有成员。

https://github.com/0voice

...全文
38 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

537

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧