一、为什么需要线程池
在面向对象编程中 ,创建和销毁对象是很耗时的,因为创建一个对象要获取内存资源或者其他更多资源.所以在日常编程中才会有意的避免过多的创建并不必要的对象。
线程的创建和销毁也是同样,而且相比于普通的对象更为消耗资源.线程池技术的引入,就是为了解决这一问题。
1、线程池简介
线程池是指在初始化一个多线程应用程序过程中创建的一个线程集合,线程池在任务未到来之前,会创建一定数量的线程放入空闲队列中.这些线程都是处于睡眠状态,即均未启动,因此不消耗CPU,只是占用很小的内存空间.当请求到来之后,线程池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。
当预先创建的线程都处于运行状态时,线程池可以再创建一定数量的新线程,用于处理更多的任务请求。
如果线程池中的最大线程数使用满了,则会抛出异常,拒绝请求.当系统比较清闲时,也可以通过移除一部分一直处于停用状态的线程,线程池中的每个线程都有可能被分配多个任务,一旦任务完成,线程回到线程池中并等待下一次分配任务。
2、线程池的优点
1. 减少创建与销毁线程带来的性能开销;
2. 可控制最大并发线程数,避免过多资源竞争而导致系统内存消耗完;
3. 能更好的控制线程的开启与回收,并且能定时执行任务。
二、线程池的实现
线程池的主要思想是将任务和执行任务的线程分开,任务可以分配给线程池里的线程执行,线程执行完当前任务之后会查看任务列表是否为空,如果不为空继续取任务执行。
其中,任务其实就是一个函数,执行任务就是执行一个特定函数的过程。所以,任务列表可以用一个存放函数指针的列表来实现。但是列表中的元素必须一致,也就是函数指针形式必须相同,如果用普通的shared_ptr去存放函数指针,无法解决这个问题。而使用c++11中的function和bind则可以很好地实现对函数的封装,使函数形式一致。
执行任务的线程们可以用一个vector来存放,但由于thread是不可复制的,所以不能直接放到vector当中,一个通用的做法是用智能指针封装线程,将智能指针存放到vector当中。
基于以上考虑,我们用lsit来存放任务,使用vector来存放用shared_ptr封装的线程。取并执行任务,添加任务,这是一个典型的生产者消费者问题,所以使用条件变量加互斥锁来控制线程之间的同步。
#include
#include
#include
#include
#include
#include
#include
#include
class ThreadPool
{
public:
using Task = std::function<void(void)>;//实现对函数的封装,使函数形式一致。
explicit ThreadPool(int num);
~ThreadPool();
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool& rhs) = delete;
void append(const Task &task);
void append(Task &&task);
void start();
void stop();
private:
bool isrunning;//线程池是否在运行
int threadNum;//线程池中线程的数量。
void work();
//使用条件变量加互斥锁来控制线程之间的同步。
std::mutex m;
std::condition_variable cond;
//用lsit来存放任务。
std::list tasks;
//执行任务的线程们可以用一个vector来存放,但由于thread是不可复制的,所以不能
//直接放到vector当中,一个通用的做法是用智能指针封装线程,将智能指针存放到vector当中。
std::vector<std::shared_ptr<std::thread>> threads;
};
//构造函数。
ThreadPool::ThreadPool(int num) : threadNum(num), isrunning(false),m()
{
}
//析构函数。
ThreadPool::~ThreadPool()
{
if (isrunning)
{
stop();
}
}
//启动线程池。
void ThreadPool::start()
{
isrunning = true;
threads.reserve(threadNum);
for (int i = 0; i < threadNum; ++i)
{
threads.push_back(std::make_shared<std::thread>(&ThreadPool::work, this));
}
printf("线程池开始运行\n");
}
//线程池关闭,并通知所有线程可以取任务了。
void ThreadPool::stop()
{
{
std::unique_lock<std::mutex> locker(m);
isrunning = false;
cond.notify_all();
}
for (int i = 0; i < threads.size(); ++i)
{
auto t = threads[i];
if (t->joinable())
t->join();//循环等待线程终止。
}
}
//往任务队列里添加任务,参数为左值。
void ThreadPool::append(const Task &task)
{
if(isrunning)
{
std::unique_lock<std::mutex> locker(m);
tasks.push_back(task);//将该任务加入任务队列。
cond.notify_one();//唤醒某个线程来执行此任务。
}
}
//往任务队列里添加任务,参数为一个右值。
void ThreadPool::append(Task &&task)
{
if(isrunning)
{
std::unique_lock<std::mutex> locker(m);
tasks.push_back(std::move(task));
cond.notify_one();
}
}
void ThreadPool::work()
{
while (isrunning)
{
Task task;
{
std::unique_lock<std::mutex> locker(m);
//如果线程池在运行,且任务列表为空,则等待有任务到来被唤醒。
if (isrunning && tasks.empty())
{
cond.wait(locker);
}
//如果任务列表不为空,无论线程池是否在运行,都需要执行完任务。
if (!tasks.empty())
{
task = tasks.front();//从任务队列中获取最开始任务。
tasks.pop_front();//将取走的任务弹出任务队列。
}
}
if(task)
task();//执行任务。
}
}
void func(int n)
{
printf("hello from %d\n", n);
}
int main()
{
ThreadPool pool(10);
pool.start();
for(int i = 0; i < 100; ++i)
{
pool.append(std::bind(func, i));
}
return 0;
}
转自:https://blog.csdn.net/kid1ing/article/details/74512698
https://www.jianshu.com/p/edab547f2710