537
社区成员
发帖
与我相关
我的任务
分享先贴上更新后的线程池全代码:
#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::move将value强制转换为右值,然后调用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;阻止了拷贝构造和赋值操作。
直接在 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++ 中,模板类的前置声明其实很麻烦。
BlockingQueue 以后增加了默认模板参数(例如 template <typename T, typename Container = std::deque<T>>),你必须在所有前置声明的地方同步修改。ThreadPool.h:只负责接口。ThreadPool.cpp:负责把 ThreadPool、BlockingQueue 和 std::thread 缝合在一起。Pimpl 的核心思想是:将类中所有的私有(private)成员变量和私有函数,全部封装到一个隐藏的结构体(或类)中,而在原本的头文件中只保留一个指向该结构体的智能指针。
目前你的 ThreadPool.h 必须包含:
<vector>(存放线程)<thread>"BlockingQueue.h"(存放任务队列)如果 BlockingQueue.h 里面又包含了大量的底层库,那么任何包含 ThreadPool.h 的地方,编译时间都会变长。使用 Pimpl 后,你的头文件会变得极其干净。
ThreadPool在头文件里,我们不再定义具体的成员变量,而是声明一个“实现类”。
#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 的所有私有成员(包括 BlockingQueue 和 vector<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));
}
vector<thread> 改成别的东西,你只需要修改 .cpp 文件。使用 ThreadPool 的第三方代码完全不需要重新编译,因为 ThreadPool.h 的结构没变(永远只有一个指针)。BlockingQueue.h 里引用了半个地球的库,它们也只会被编译一次(在 ThreadPool.cpp 中),而不会顺着头文件蔓延到整个项目。Post 等公开接口,看不到你内部实现的私有成员。