syncqueue.hpp
cpp
//同步队列
#ifndef SYNCQUEUE_HPP
#define SYNCQUEUE_HPP
#include<iostream>
#include<queue>
#include<list>
#include<vector>
#include<thread>
#include<mutex>
#include<functional>
#include<condition_variable>
#include <chrono>
using namespace std;
template<typename T>
class syncqueue{
private:
list<T>m_queue;
mutable mutex m_mutex;
condition_variable m_notempty;//队列非空(通知取数据)
condition_variable m_notfull;//非满(通知存数据)
int m_maxsize; //队列最大容量
bool m_needstop;
bool isfull()const{
return m_queue.size()>=m_maxsize;
}
bool isempty()const{
return m_queue.empty();
}
template<typename Y>
void add(Y&& task){
unique_lock<mutex>locker(m_mutex);
m_notfull.wait(locker,[this]{return m_needstop||!isfull();});
if(m_needstop)return;
m_queue.push_back(forward<Y>(task));//完美转发插入任务
m_notempty.notify_one();//通知等待的线程
}
public:
syncqueue(int maxsize=100):m_maxsize(maxsize),m_needstop(false){}
~syncqueue(){
stop();
}
void put(const T&task){//存入任务
add(task);
}
void put(T&&task){//右值版本
add(forward<T>(task));
}
void take(list<T>&list){//一次性全取出
unique_lock<mutex>locker(m_mutex);
m_notempty.wait(locker,[this]{return m_needstop||!isempty();});
if(m_needstop)return;
list=move(m_queue);
m_notfull.notify_one();
}
void take(T&task){//取出头部一个
unique_lock<mutex>locker(m_mutex);
m_notempty.wait(locker,[this]{return m_needstop||!isempty();});
if(m_needstop)return;
task=m_queue.front();
m_queue.pop_front();
m_notfull.notify_one();
}
void stop(){
lock_guard<mutex>locker(m_mutex);
m_needstop=true;
m_notempty.notify_all();
m_notfull.notify_all();
}
bool empty()const{
lock_guard<mutex>locker(m_mutex);
return m_queue.empty();
}
bool full()const{
lock_guard<mutex>locker(m_mutex);
return m_queue.size()>=m_maxsize;
}
size_t size()const{
lock_guard<mutex>locker(m_mutex);
return m_queue.size();
}
int maxsize()const{
return m_maxsize;
}
};
#endif
fixed_pthreadpoll
cpp
#ifndef FIXED_THREAD_POOL_HPP
#define FIXED_THREAD_POOL_HPP
#include<iostream>
#include"syncqueue.hpp"
#include<atomic>
#include <chrono>
using namespace std;
class fixedthreadpool{
public:
using task=function<void()>;
private:
vector<thread>m_threads;
syncqueue<task>m_queue;
atomic<bool>m_running;//原子变量 无需加锁
once_flag m_stopflag;//只允许改变一次
void runthread(){
while(m_running){
task t;
m_queue.take(t);
if(t&&m_running)task();
}
}
void stopthreads(){
m_queue.stop();
m_running=false;
for(auto&thread : m_threads){
if(thread.joinable())thread.join();
}
m_threads.clear();
}
public:
fixedthreadpool(int numthreads=thread::hardware_concurrency()):m_running(true){//当前系统硬件并发数 cpu核心数
if(numthreads<=0)numthreads=thread::hardware_concurrency();
for(int i=0;i<numthreads;i++){
m_threads.emplace_back(&fixedthreadpool::runthread,this);
}
}
~fixedthreadpool(){
stop();
}
void stop(){
call_once(m_stopflag,[this]{stopthreads();});
}
template<typename Y>
void addtask(Y&&task){
m_queue.put(forward<Y>(task));
}
size_t threadcount()const{
return m_threads.size();
}
};
#endif
优化一:拒绝策略
线程池的拒绝策略
AbortPolicy :中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
DiscardPolicy:抛弃任务和策略。什么都不做,直接抛弃被拒绝的任务。
DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么"抛弃最旧的"策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。
CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
1. 拒绝策略枚举
cpp
// 在FixedThreadPool.h中添加
enum class RejectPolicy {
ABORT, // 直接抛异常(默认)
CALLER_RUNS, // 调用者线程执行
DISCARD, // 静默丢弃
DISCARD_OLDEST // 丢弃最老任务
};
2. 修改SyncQueue支持拒绝策略
cpp
// 在SyncQueue.h中修改Add函数
template<typename F>
bool Add(F&& task, RejectPolicy policy = RejectPolicy::ABORT) {
std::unique_lock<std::mutex> locker(m_mutex);
// 等待队列不满
if (!m_notFull.wait_for(locker, std::chrono::milliseconds(100),
[this] { return m_needStop || !IsFull(); })) {
// 超时,队列仍满,根据策略处理
switch (policy) {
case RejectPolicy::ABORT:
throw std::runtime_error("任务队列已满");
case RejectPolicy::DISCARD:
return false; // 静默丢弃
case RejectPolicy::CALLER_RUNS:
locker.unlock();
task(); // 调用者线程执行
return true;
case RejectPolicy::DISCARD_OLDEST:
if (!m_queue.empty()) {
m_queue.pop_front(); // 丢弃最老任务
m_queue.push_back(std::forward<F>(task));
m_notEmpty.notify_one();
return true;
}
// 如果队列空但还超时(不应该发生)
return false;
default:
throw std::runtime_error("未知的拒绝策略");
}
}
if (m_needStop) return false;
m_queue.push_back(std::forward<F>(task));
m_notEmpty.notify_one();
return true;
}
3. 修改FixedThreadPool
cpp
class FixedThreadPool {
private:
// 添加成员变量
RejectPolicy m_policy;
public:
// 修改构造函数
FixedThreadPool(int numThreads = std::thread::hardware_concurrency(),
RejectPolicy policy = RejectPolicy::ABORT)
: m_running(true), m_policy(policy) {
// ... 创建线程
}
// 修改AddTask
template<typename F>
bool AddTask(F&& task) {
return m_queue.Put(std::forward<F>(task), m_policy);
}
// 设置拒绝策略
void SetRejectPolicy(RejectPolicy policy) {
m_policy = policy;
}
};
🧪 测试拒绝策略
cpp
void TestRejectPolicy() {
std::cout << "=== 测试拒绝策略 ===" << std::endl;
// 创建很小的线程池和队列
FixedThreadPool pool(1, RejectPolicy::ABORT);
// 快速提交大量任务
try {
for (int i = 0; i < 1000; i++) {
pool.AddTask([i]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
}
} catch (const std::exception& e) {
std::cout << "捕获异常: " << e.what() << std::endl;
}
// 测试CALLER_RUNS策略
FixedThreadPool pool2(1, RejectPolicy::CALLER_RUNS);
std::cout << "开始提交任务(CALLER_RUNS策略)..." << std::endl;
for (int i = 0; i < 10; i++) {
pool2.AddTask([i]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
});
}
std::this_thread::sleep_for(std::chrono::seconds(2));
}
优化二:改进addtask
改写AddTask函数降低客户端得到返回值的难度
版本1:
cpp
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>
using RetType = decltype(func(args...));
std::packaged_task<int()> task(std::bind(func, args...));
std::future<RetType> result = task.get_future();
task();
return result;
版本2:
cpp
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>
using RetType = decltype(func(args...));
std::packaged_task<RetType()> task(std::bind(std::forward<Func>(func),
std::forward<Args>(args)...));
std::future<RetType> result = task.get_future();
task();
return result;
版本3:
cpp
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>
using RetType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RetType()> >
(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
std::future<RetType> result = task->get_future();
if (m_queue.put([task] {(*task)(); }) != 0)
{
(*task)();
}
return result;
选最好的那个
支持任意函数和参数的AddTask
cpp
// 在FixedThreadPool类中添加
template<typename Func, typename... Args>
auto Submit(Func&& func, Args&&... args)
-> std::future<decltype(func(args...))> {
// 获取返回类型
using ReturnType = decltype(func(args...));
// 创建packaged_task
auto task = std::make_shared<std::packaged_task<ReturnType()>>(
std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
);
// 获取future
std::future<ReturnType> result = task->get_future();
// 包装成void()函数放入队列
auto wrapper = [task]() { (*task)(); };
// 添加任务
if (!AddTask(std::move(wrapper))) {
// 如果添加失败(队列满),根据策略处理
if (m_policy == RejectPolicy::CALLER_RUNS) {
(*task)(); // 调用者执行
} else {
throw std::runtime_error("任务提交失败");
}
}
return result;
}
使用示例
cpp
void TestImprovedAddTask() {
std::cout << "=== 测试改进的AddTask ===" << std::endl;
FixedThreadPool pool(4);
// 现在可以这样提交任务!
auto future1 = pool.Submit([](int a, int b) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return a + b;
}, 10, 20);
auto future2 = pool.Submit([](const std::string& s) {
return "Hello, " + s;
}, "World");
// 获取结果
std::cout << "结果1: " << future1.get() << std::endl; // 30
std::cout << "结果2: " << future2.get() << std::endl; // Hello, World
// 甚至支持成员函数
struct Calculator {
int multiply(int a, int b) { return a * b; }
};
Calculator calc;
auto future3 = pool.Submit(&Calculator::multiply, &calc, 5, 6);
std::cout << "结果3: " << future3.get() << std::endl; // 30
}
FixedThreadPool 的使用场景
-
并发限制:当有大量任务需要执行,但希望限制并发线程数时,可以使用 FixedThreadPool。它可以控制并发执行的线程数量,避免系统资源过度占用和线程竞争导致性能下降。
-
稳定且可控的任务执行:当任务量稳定且任务的执行时间较短时,FixedThreadPool 是一个合适的选择。由于线程池中的线程数量固定,可以提供稳定的执行环境,避免频繁地创建和销毁线程的开销。
-
服务器应用:FixedThreadPool 适用于服务器中需要处理大量请求的场景。可以根据服务器的硬件配置和预估的负载情况,设置线程池的大小,以提供最佳的性能和资源利用率。
-
批量任务处理:当需要对一批任务进行并发处理时,FixedThreadPool 可以提供线程池管理和调度的支持。例如,批量数据的处理、文件的批量上传或下载等,都可以使用 FixedThreadPool 来实现并发处理。
需要注意的是,由于 FixedThreadPool 的线程数是固定的,如果任务量超过了线程池中线程的最大数量,并且任务排队队列已满,新提交的任务将会被拒绝。因此,在使用 FixedThreadPool 时,需要根据系统的负载情况、任务的特性和对任务处理的需求来合理地配置线程池的大小。
用C++11的线程相关的特性让我们编写并发程序变得简单,比如可以利用线程、条件变量、互斥量来实现一个轻巧的线程池,从而避免频繁地创建线程。使用线程池也需要注意一些问题,比如要保证线程池中的任务不能挂死,否则会耗尽线程池中的线程,造成假死现象;还要避免长时间去执行一个任务,会导致后面的任务大量堆积而得不到及时处理,对于耗时较长的任务可以考虑用单独的线程去处理。