一、概述
工作窃取式线程池采用了工作窃取算法,具体来说就是当某个线程执行完自己队列中的任务后,会从其他线程的队列中“偷取”任务来执行。这种算法可以提高线程利用率,减少线程之间的竞争,以及减小线程的等待时间。
在同步队列中设计std::vector<std::list<T>>,使用该容器来存储任务,利用数组加链表,设置vector的大小为bucketsize,即一般为CPU核数,利用链表存放具体任务。当index下标的list容器中任务处理结束,则使用threadIndex生成bucketSize范围内不为index下标的new_index,然后线程可以去处理该list容器中的任务。
设置m_waitTime,即为当任务缓冲区中满或者空时,生产者和消费者的等待时间(在此利用条件变量->wait_for)当发生超时,则生产者和消费者退出。在线程池中存在异步运行策略,即当添加任务失败时,可以先执行此任务。任务使用function和bind结合的形式进行传递,即using task=std::function<void(void)>,bind绑定任务函数名以及参数,通过function可调用对象包装器执行。
WorkStealingPool可以设定多个工作线程,每个工作线程都有一个自己的任务队列,每个线程在执行任务时会首先从自己的队列中获取任务,如果自己队列为空,则从其他线程的队列中获取任务。这种设计可以充分发挥多核处理器的并行能力,提高整体的任务处理效率。
二、同步队列的设计
1.第一种情况:给所有的桶上一个锁(代码如下)
#ifndef SYNCQUEUE4_HPP//等待同步队列
#define SYNCQUEUE4_HPP//这个是给所有桶一个锁,相互联系
#include<iostream>
#include<list>
#include<vector>
#include<mutex>;
#include<condition_variable>
#include<chrono>
using namespace std;
using namespace std::chrono;
template<class Task>
class SyncQueue
{
private:
std::vector<std::list<Task>>m_queue;//桶
size_t m_backetSize;//桶的大小(vector)与threadnum相关联
size_t m_maxSize;//每一个桶的任务上限(list里面的任务) 每一个桶里面的list的数量都不能超过每一个桶的上限
mutable std::mutex mux;
std::condition_variable cv_x;//消费者
std::condition_variable cv_s;//生产者
std::condition_variable m_waitStop;//等待停止
size_t maxsize;//任务上限
bool m_stop;//false-->没停止(判断同步队列是否停止)
size_t m_waitTime = 10;//10ms的等待时间
size_t TotoTaskCount()const //总的任务数
{
size_t sum = 0;
for (auto& tlist : m_queue)
{
sum += tlist.size();
}
return sum;
}
bool IsFull(int index)const
{
return m_queue[index].size() >= maxsize;
}
bool IsEmpty(int index)const
{
return m_queue[index].empty();
}
template<class F>
int Add(F&& f, int index)//添加到生产者 index桶的编号
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsFull(index))
{
//cv_s.wait(lock);
if (std::cv_status::timeout == cv_s.wait_for(lock, std::chrono::milliseconds(m_waitTime)))
{
return -1;
}
}
//cv_s.wait(lock, [this]()->bool {return !m_stop && !IsFull() });
if (m_stop)//同步队列停止
{
return -2;
}
m_queue[index].push_back(std::forward<F>(f));
cv_x.notify_all();
return 0;
}
public:
SyncQueue(size_t max , size_t bucketsize ) :maxsize(max), m_stop(false)
{
m_queue.resize(bucketsize);//resize会对每个桶里面进行初始化为0
}
~SyncQueue()
{
}
void Stop()//强制停止-->不管同步队列有没有值都进行停止
{
{
std::unique_lock<std::mutex>lock(mux);
m_stop = true;
}
cv_x.notify_all();
cv_s.notify_all();
}
void WaitStop()//等待停止-->等同步队列里的值都进行完了才进行停止
{
std::unique_lock<std::mutex>lock(mux);
while (TotoTaskCount() != 0)
{
//m_waitStop.wait(lock);
m_waitStop.wait_for(lock, std::chrono::milliseconds(1));//等待1ms,超过1ms后自己就启动
}
m_stop = true;
cv_x.notify_all();
cv_s.notify_all();
}
int Put(Task&& task, int index)//添加任务(生产者) index桶的编号
{
return Add(std::forward<Task>(task), index);
}
int Put(const Task& task, int index)//添加任务(生产者)
{
return Add(task, index);
}
int Take(std::list<Task>& val,int index)//消费者-->一次消费所有
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsEmpty(index))
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = std::move(m_queue[index]);
cv_s.notify_all();
return 0;
}
int Take(Task& val, int index)//消费者-->一次消费一个
{
std::unique_lock<std::mutex>lock(mux);
while (!m_stop && IsEmpty(index))
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = m_queue[index].front();
m_queue[index].pop_front();
cv_s.notify_all();
return 0;
}
bool Empty()const//判空
{
std::unique_lock<std::mutex>lock(mux);
return TotoTaskCount() == 0;
}
bool Full()const //判满
{
std::unique_lock<std::mutex>lock(mux);
return TotoTaskCount() >= maxsize * m_backetSize;
}
bool BuckEmpty(int index)const//某个桶判空
{
std::unique_lock<std::mutex>lock(mux);
return m_queue[index].empty();
}
bool BuckFull(int index)const //某个桶判满
{
std::unique_lock<std::mutex>lock(mux);
return m_queue[index].size() >= maxsize;
}
size_t BuckSize(int index)//某个桶的大小
{
std::unique_lock<std::mutex>lock(mux);
return m_queue[index].size();
}
size_t Size()const //队列中元素个数
{
std::unique_lock<std::mutex>lock(mux);
return TotoTaskCount();
}
size_t Count()const //队列中元素个数
{
return TotoTaskCount();
}
};
#endif
2.给每一个桶一个锁,相互独立 ---->时间比4更长,但并发度更好
#ifndef SYNCQUEUE5_HPP//等待同步队列
#define SYNCQUEUE5_HPP//这个是给每一个桶一个锁,相互独立 ---->时间比4更长,但并发度更好
#include<iostream>
#include<list>
#include<vector>
#include<mutex>;
#include<condition_variable>
#include<chrono>
#include<memory>
#include<atomic>
using namespace std;
using namespace std::chrono;
template<class Task>
class SyncQueue
{
private:
std::vector<std::list<Task>>m_queue;//桶
size_t m_backetSize;//桶的大小(vector)与threadnum相关联
size_t m_maxSize;//每一个桶的任务上限(list里面的任务) 每一个桶里面的list的数量都不能超过每一个桶的上限
//mutable std::mutex mux[m_backetSize];
std::unique_ptr<std::mutex[]>mux;//动态创建锁
std::condition_variable cv_x;//消费者
std::condition_variable cv_s;//生产者
std::condition_variable m_waitStop;//等待停止
size_t maxsize;//任务上限
std::atomic_bool m_stop;//false-->没停止(判断同步队列是否停止)
size_t m_waitTime = 10;//10ms的等待时间
size_t TotoTaskCount()const //总的任务数
{
size_t sum = 0;
for (auto& tlist : m_queue)
{
sum += tlist.size();
}
return sum;
}
bool IsFull(int index)const
{
return m_queue[index].size() >= maxsize;
}
bool IsEmpty(int index)const
{
return m_queue[index].empty();
}
template<class F>
int Add(F&& f, int index)//添加到生产者 index桶的编号
{
std::unique_lock<std::mutex>lock(mux[index]);
while (!m_stop && IsFull(index))
{
//cv_s.wait(lock);
if (std::cv_status::timeout == cv_s.wait_for(lock, std::chrono::milliseconds(m_waitTime)))
{
return -1;
}
}
//cv_s.wait(lock, [this]()->bool {return !m_stop && !IsFull() });
if (m_stop)//同步队列停止
{
return -2;
}
m_queue[index].push_back(std::forward<F>(f));
cv_x.notify_all();
return 0;
}
public:
SyncQueue(size_t max, size_t bucketsize) :maxsize(max), m_stop(false),m_backetSize(bucketsize)
{
m_queue.resize(bucketsize);//resize会对每个桶里面进行初始化为0
mux = std::make_unique<std::mutex[]>(m_backetSize);//创建一组对象
}
~SyncQueue()
{
}
void Stop()//强制停止-->不管同步队列有没有值都进行停止
{
{
//std::unique_lock<std::mutex>lock(mux);
m_stop = true;
}
cv_x.notify_all();
cv_s.notify_all();
}
void WaitStop()//等待停止-->等同步队列里的值都进行完了才进行停止
{
for (int i = 0; i < m_backetSize; i++)//逐个等待每个桶
{
std::unique_lock<std::mutex>lock(mux[i]);
while (!IsEmpty(i))
{
//m_waitStop.wait(lock);
m_waitStop.wait_for(lock, std::chrono::milliseconds(1));//等待1ms,超过1ms后自己就启动
}
cv_x.notify_all();
cv_s.notify_all();
}
m_stop = true;
}
int Put(Task&& task, int index)//添加任务(生产者) index桶的编号
{
return Add(std::forward<Task>(task), index);
}
int Put(const Task& task, int index)//添加任务(生产者)
{
return Add(task, index);
}
int Take(std::list<Task>& val, int index)//消费者-->一次消费所有
{
std::unique_lock<std::mutex>lock(mux[index]);
while (!m_stop && IsEmpty(index))
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = std::move(m_queue[index]);
cv_s.notify_all();
return 0;
}
int Take(Task& val, int index)//消费者-->一次消费一个
{
std::unique_lock<std::mutex>lock(mux[index]);
while (!m_stop && IsEmpty(index))
{
//cv_x.wait(lock);
if (std::cv_status::timeout == cv_x.wait_for(lock, std::chrono::seconds(1)))//看是否超时
{
return -1;
}
}
if (m_stop)
{
return -2;
}
val = m_queue[index].front();
m_queue[index].pop_front();
cv_s.notify_all();
return 0;
}
size_t Count()const //队列中元素个数
{
return TotoTaskCount();
}
};
#endif
三.线程池设计
#pragma once
#include"SyncQueue5_WorkStealingPool.hpp"//工作窃取线程池-->更适用于一致性哈希的实现
#include<iostream>
#include<atomic>
#include<functional>
#include<list>
#include<vector>
#include<thread>
#include<chrono>
#include<mutex>
#include<future>
#include<condition_variable>
#include<memory>
class WorkStealingPool//提交任务和获取任务很重要
{
public:
using Task = std::function<void(void)>;
private:
SyncQueue<Task>m_taskQueue;
size_t m_numThreads;//总线程数
std::vector<std::unique_ptr<std::thread>>m_threadgroup;//管理线程
std::atomic_bool m_running = false;
std::once_flag m_flag;
int threadIndex()const//获得线程的桶号
{
static int num = 0;
return ++num % m_numThreads;//要存放在哪个桶里面,进行随机化
}
void Start(int numthreads)//开始线程
{
m_running = true;
for (int i = 0; i < numthreads; i++)//i为线程编号
{
m_threadgroup.push_back(std::make_unique<std::thread>(
&WorkStealingPool::RunningThread, this, i));
}
}
void RunningThread(const int tindex)//线程的入口函数
{
while (m_running)
{
std::list<Task>tasklist;
if (m_taskQueue.Take(tasklist, tindex) == 0)
{
for (auto& task : tasklist)
{
task();
}
}
else
{
int i = threadIndex();
while (i == tindex)
{
i = threadIndex();
}
if (i != tindex && m_taskQueue.Take(tasklist, i) == 0)
{
cout << "偷取任务成功" << endl;
for (auto& task : tasklist)
{
task();
}
}
else
{
std::this_thread::yield();//偷取失败让其他线程去运行
}
}
}
}
void stopThread()//停止线程
{
m_taskQueue.WaitStop();
m_running = false;
for (auto& tha : m_threadgroup)
{
if (tha && tha->joinable())//joinable看线程是否还活动
{
tha->join();//等待线程结束
}
}
m_threadgroup.clear();
}
public:
WorkStealingPool(int qsize = 100, int numthreads = 8) :m_taskQueue(qsize, numthreads), m_numThreads(numthreads)//每一个桶里面元素队列大小qsize
{
Start(numthreads);
}
~WorkStealingPool()
{
Stop();
}
void Stop()
{
std::call_once(m_flag, [this]()->void {stopThread(); });
}
template<class Func, class ... Args>//可变模板参
auto AddTask(Func&& func, Args&& ... 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_taskQueue.Put([task] {(*task)(); }, threadIndex()) != 0)//拒绝策略
{
cout << "not add........" << endl;
(*task)();
}
return result;
}
void excute(const Task& task)
{
if (m_taskQueue.Put(task, threadIndex()) != 0)//拒绝策略
{
cout << "not add........" << endl;
task();
}
}
void excute(Task&& task)
{
if (m_taskQueue.Put(std::forward<Task>(task), threadIndex()) != 0)
{
cout << "not add........" << endl;
task();
}
}
};
四.测试用例
#include"WorkStealingPool.hpp"
#include<thread>
#include<vector>
#include<iostream>
#include<algorithm>
#include<chrono>//时间库
#include<future>//未来体
using namespace std;
using namespace std::chrono;
//------------------------排序测试
std::atomic_int randnum{ 0 };//随机化的此时
std::atomic_int sortnum{ 0 };//排序的次数
void my_print(std::vector<int>& arr)
{
for (auto& x : arr)
{
cout << x << " ";
}
cout << "--------------------------" << endl;
}
void my_rand(std::vector<int>& arr)
{
int n = arr.capacity();
for (int i = 0; i < n; i++)
{
arr.push_back(rand() % 1000000);
}
++randnum;
}
void my_sort(std::vector<int>& arr)
{
std::sort(arr.begin(), arr.end());
++sortnum;
}
int main()
{
const int n = 1000;
const int elemnum = 10000;
std::vector<std::vector<int>>arr;
arr.resize(n);
time_point<high_resolution_clock>begin;//高精度的时间点
begin = high_resolution_clock::now();//得到当前时间
{
WorkStealingPool mypool(100,16);
for (int i = 0; i < n; i++)
{
arr[i].reserve(elemnum);
mypool.excute(std::bind(my_rand, std::ref(arr[i])));
}
cout << "随机化值完成---------------------" << endl;
}
cout << "随机初始化值为:======================" << randnum << endl;
{
cout << "排序开始-----------------------------------------------" << endl;
WorkStealingPool mypool(100,16);
for (int i = 0; i < n; i++)
{
mypool.excute(std::bind(my_sort, std::ref(arr[i])));
}
std::this_thread::sleep_for(std::chrono::seconds(15));
cout << "排序结束-----------------------------------------------" << endl;
}
std::int64_t diff = duration_cast<microseconds>(high_resolution_clock::now() - begin).count();//duration_cast<microseconds>强转为微妙,并cout数字化
cout << " 所用时间差diff为: " << diff << " ms " << endl;
cout << "初始化次数为------------" << randnum << endl;
cout << "排序的次数为------------" << sortnum << endl;
return 0;
}
五、适用场景
1、任务分解型应用:当一个任务需要被分解成多个子任务进行并行处理时,工作窃取式线程池可以自动管理任务的分配和调度,充分利用多核处理器的并行能力,提高任务处理效率。
2、递归型任务:对于递归型的任务,workstealingpool能够适应任务的动态变化,根据需要创建和调度子任务,以实现更高效的递归执行。
3、高吞吐量任务:WorkStealingPool的工作窃取算法可以减小线程之间的竞争,并且能够在任务队列为空时从其他线程窃取任务,从而减少线程的等待时间,提高整体的任务处理吞吐量。适用于高吞吐量的任务场景。
4、CPU密集型任务:对于需要大量的CPU计算而没有I.O阻塞的任务,使用工作窃取式线程池可以更好地充分利用CPU核心,并且可以根据需要增加或减小线程数量,以适应任务的计算量。