为了减少线程切换等操作所带来的开销,这里介绍一个简单的线程池的实现过程。
首先线程池主要由两个部分组成,分别是存放线程的容器和存放任务的队列。设计思路如下图流程图所示:线程池主要有两个执行函数。其中一个函数负责运行任务,也就是每个线程的运行实体,另一个函数负责放置任务到线程对应的队列。
线程池的定义如下:这里面thread_pool_vec是线程的存放容器;task_queue存放任务队列,每个线程对应自己的任务队列,使用双向队列的存放任务,是因为我认为双向队列为任务的存取提供更灵活的选择。锁和条件资源由于不支持拷贝,我把它们声明为指针变量,当然,我认为指针容器类型存放锁是更好的选择,避免忘记delete或重复delete等等问题。
#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <deque>
#include <functional>
#include <condition_variable>
typedef std::function<void()> Task; //任务的模板
class ThreadPool
{
private:
std::vector<std::thread> thread_pool_vec; //线程池的容器
std::vector<std::deque<Task>> task_queue; //任务队列
std::vector<bool> is_running; //运行标志
std::mutex* mutex_queue; //读写锁队列
std::condition_variable* not_empty; //非空条件变量
std::condition_variable* not_full; //非满变量
int num_of_thread; //线程的数量
public:
ThreadPool(const int& thread_num)
{
//根据thread_num初始化线程池的大小
if(thread_num > 0)
{
thread_pool_vec.reserve(thread_num);
task_queue.reserve(thread_num);
start(thread_num);
}
}
~ThreadPool()
{
delete []mutex_queue; //析构时记得把原始指针的变量都释放了
delete []not_empty;
delete []not_full;
mutex_queue = NULL;
not_empty = NULL;
not_full = NULL;
}
//放任务的函数
bool addTask(Task task, const int& index);
private:
//运行任务的函数
void executeTask(int index);
//启动线程的函数
bool start(const int& thread_num);
//终止线程的函数
bool stop(const int& index);
bool isFull(const int& index);
};
#endif
出于安全考虑,我还是使用了指针容器。需要注意,使用指针容器下标获取的元素,是解引用后的元素,不需要手动解引用。
#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <deque>
#include <functional>
#include <condition_variable>
#include <boost/ptr_container/ptr_vector.hpp>
typedef std::function<void()> Task; //任务的模板
class ThreadPool
{
private:
std::vector<std::thread> thread_pool_vec; //线程池的容器
std::vector<std::deque<Task>> task_queue; //任务队列
boost::ptr_vector<std::mutex> mutex_queue; //读写锁队列
std::vector<bool> is_running; //运行标志
boost::ptr_vector<std::condition_variable> not_empty; //非空条件变量
boost::ptr_vector<std::condition_variable> not_full; //非满变量
int num_of_thread; //线程的数量
public:
ThreadPool(const int& thread_num)
{
//根据thread_num初始化线程池的大小
if(thread_num > 0)
{
thread_pool_vec.reserve(thread_num);
task_queue.reserve(thread_num);
mutex_queue.reserve(thread_num);
not_empty.reserve(thread_num);
not_full.reserve(thread_num);
start(thread_num);
}
}
~ThreadPool()
{
}
//放任务的函数
bool addTask(Task task, const int& index);
private:
//运行任务的函数
void executeTask(int index);
//启动线程的函数
bool start(const int& thread_num);
//终止线程的函数
bool stop(const int& index);
bool isFull(const int& index);
};
#endif
由上面代码可见,任务的实体Task,是一个无参数的函数模板,所以在调用是需要注意绑定参数。
下面是整个线程池的具体实现:
#include "./thread_pool.h"
bool ThreadPool::isFull(const int& index)
{
int que_size = task_queue[index].size();
int num_size = 10; //此处设置了一个任务队列的最大值,需要实验验证是否合理
return que_size > num_size ? true : false;
}
bool ThreadPool::addTask(Task task, const int& index)
{
std::unique_lock<std::mutex> mutex_lock((mutex_queue[index]));
//判断是否已经满队了
while(isFull(index))
{
std::cout<<"index: "<<index<<"task_queue is full"<<std::endl;
//调用条件变量
not_full[index].wait(mutex_lock);
}
task_queue[index].push_back(task);
//只有一个线程使用,所以使用notify_one()
not_empty[index].notify_one();
return true;
}
void ThreadPool::executeTask(int index)
{
while(is_running[index])
{
std::unique_lock<std::mutex> mutex_lock((mutex_queue[index]));
//判断队列是否为空
while(task_queue[index].empty())
{
std::cout<<"index: "<<index<<"task_queue is empty"<<std::endl;
//调用条件变量
not_empty[index].wait(mutex_lock);
}
//取任务队列首个任务
if(task_queue[index].front() != NULL)
{
Task task = task_queue[index].front();
task_queue[index].pop_front();
//执行任务
task();
}
not_full[index].notify_one();
}
}
bool ThreadPool::start(const int& thread_num)
{
num_of_thread = thread_num;
//循环开启线程
for(int i = 0; i < thread_num; i++)
{
thread_pool_vec.push_back(std::thread(&ThreadPool::executeTask, this, i));
thread_pool_vec[i].detach(); //分离线程
mutex_queue.push_back(new std::mutex());
is_running.push_back(true);
not_empty.push_back(new std::condition_variable());
not_full.push_back(new std::condition_variable());
std::deque<Task> task_deque;
task_queue.push_back(task_deque);
}
return true;
}
bool ThreadPool::stop(const int& index)
{
//检查线程是否还在运行
if(is_running[index] == false)return false;
//停止线程
is_running[index] = false;
return true;
}
其中我是用的是一对条件变量,一个为非满条件,另一个未非空条件。将条件变量设置为一个,逻辑上找不到问题,但效率是否会被影响,暂时未证实。
#include"../thread_pool/thread_pool.h"
void func(int index)
{
std::cout<<"Task: "<<index<<std::endl;
}
int main()
{
int num = 0;
std::cin>>num;
ThreadPool *threadpool = new ThreadPool(num);
while(true)
{
srand((unsigned int)time(NULL));
int index = rand()%num;
Task task = std::bind(func, index);
threadpool->addTask(task, index);
}
delete threadpool;
threadpool = NULL;
return 0;
}
按照以上代码做的测试结果:明显看出由于任务放置速度与线程运行任务的速度不匹配,造成很多线程对应的任务队列为空。(工人多,订单数,恰好反映了现在工厂的情况)