目录
线程池
设计线程池的关键问题
代码
可能出现的疑问
queue> task;
总结:
template auto InsertQueue(T&& t, Args&& ...args)->future;(t(args...))>
总结:
ThreadPool(size_t size);构造函数
总结:
templateauto ThreadPool::InsertQueue(T&& t, Args&& ...args)->future(t(args...))>
总结:
线程池
线程池是一种用于管理和控制线程的机制。它包含一个固定数量的线程组成的池子,线程池可以接收任务并分配线程来执行这些任务。线程池的主要目的是通过减少线程的创建和销毁次数,来提高线程的复用性和效率。
线程池的作用包括以下几个方面:
1. 提高性能:线程池可以避免频繁地创建和销毁线程,减少了系统开销,提高了响应速度和处理能力。
2. 提供线程管理和控制:线程池可以统一管理线程的创建、销毁和调度,可以根据任务的情况动态调整线程数目。
3. 避免资源竞争:线程池可以有效地避免多个线程同时访问共享资源导致的竞争问题,通过控制并发访问,提高了程序的稳定性和安全性。
4. 提供任务队列:线程池中的任务可以被放入任务队列中,按照先进先出的顺序执行,确保任务的顺序性和有序性。
设计线程池的关键问题
1.可使用的线程数量
2.高效的分配任务的方式
3.如何等待任务完成
代码
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<functional> //提供了函数对象和函数模板
#include<future> //异步操作
#include<exception> //异常用头文件
using namespace std;
class ThreadPool
{
public:
ThreadPool(size_t size);
~ThreadPool();
//函数模板
template<class T,class ...Args>
auto InsertQueue(T&& t, Args&& ...args)->future<decltype(t(args...))>;
protected:
queue<function<void()>> task; //任务列表
vector<thread> workers; //工作线程
mutex mtx_queue;
condition_variable cv;
bool stop;
};
//测试函数
int func(int i)
{
printf("线程-%d-进行工作\n", i);
//this_thread::sleep_for(1s);
printf("线程-%d-工作结束\n", i);
return i * i;
}
int main()
{
ThreadPool pool(4);//创建线程池,4个线程
vector<future<int>> result;
for (int i = 0; i < 1000; i++)
{
result.emplace_back(pool.InsertQueue(func, i));
}
for (auto&& v:result)
{
printf("结果:%d\n", v.get());
}
return 0;
}
ThreadPool::ThreadPool(size_t size):stop(false)
{
for (int i = 0; i < size; i++)
{
workers.emplace_back([this]
{
while (true)
{
function<void()> task;
{
unique_lock<mutex> lock(this->mtx_queue);
this->cv.wait(lock, [this] {return this->stop || !this->task.empty(); });
if (this->stop &&this->task.empty())
{
return;
}
task = move(this->task.front());
this->task.pop();
}
task();
}
});
}
}
ThreadPool::~ThreadPool()
{
{
unique_lock<mutex> lock(mtx_queue);
stop = true;
}
cv.notify_all();
for (auto &t :workers)
{
t.join();
}
}
template<class T, class ...Args>
auto ThreadPool::InsertQueue(T&& t, Args&& ...args)->future<decltype(t(args...))>
{
using Type = decltype(t(args...));
auto task = make_shared<packaged_task<Type()>>(bind(forward<T>(t), forward<Args>(args)...));
future<Type> res = task->get_future();
{
unique_lock<mutex> lock(mtx_queue);
if (stop)
{
throw runtime_error("停止。。");
}
this->task.emplace([task]
{
(*task)();
});
}
cv.notify_one();
return res;
}
可能出现的疑问
queue<function<void()>> task;
这是一个队列(queue),其中存储了一系列的函数对象(function),这些函数对象中的函数类型为void(),即没有参数并且无返回值的函数。
这样的设计通常用于实现任务调度或异步操作。
总结:
通过将需要执行的任务(函数)依次添加到队列中,可以实现任务的顺序执行,避免了函数之间的并发访问问题。调用者可以按照自己的需求,从队列中取出函数对象并进行执行,从而实现任务的调度和执行。
template<class T,class ...Args>
auto InsertQueue(T&& t, Args&& ...args)->future<decltype(t(args...))>;
这是一个函数模板,用于将任务插入到队列中并返回一个future对象。
它有两个模板参数:T和Args。
T是任务的类型,它可以是一个函数指针、函数对象或lambda表达式,表示要执行的任务。
Args是可变参数模板,表示任务执行所需的参数。
函数模板的返回类型是一个auto类型,通过decltype(t(args...))来推导:这意味着返回类型将根据传入的任务类型和参数类型而定。
该函数将接受一个任务t和一系列参数args,并将它们传递给任务t进行执行。
然后,它将返回一个future对象,用于获取任务执行的结果。
总结:
使用这个函数模板,可以方便地将任务插入到队列中,并在需要时获取任务的执行结果。
ThreadPool(size_t size);构造函数
这是一个线程池的实现。首先,在构造函数中初始化一个指定大小的线程池,并使用一个布尔变量 stop
作为停止标志。然后,通过循环创建指定数量的线程,每个线程都执行一个 lambda 表达式。
lambda 表达式是一个无限循环,它首先获取线程池的锁 mtx_queue
,然后等待条件变量 cv
的通知,条件是线程池停止或任务队列为空。如果满足条件,线程将退出循环。否则,它会从任务队列中取出一个任务并执行。
总结:
整个过程中,线程池使用互斥锁 mtx_queue
来保证多个线程对任务队列的访问是线程安全的。同时,条件变量 cv
用于在任务队列为空或线程池停止时进行通知和等待。
template<class T, class ...Args>
auto ThreadPool::InsertQueue(T&& t, Args&& ...args)->future<decltype(t(args...))>
InsertQueue
函数的目的是将一个任务添加到线程池的任务队列中,并返回一个 future
对象,用于获取任务执行的结果。
该函数使用了可变参数模板和完美转发,可以接受任意类型的任务和参数。在函数内部,它首先使用 decltype
推导出任务的返回类型,并创建一个 packaged_task
对象来封装任务。
接下来,函数获取任务的 future
对象,并在一个作用域内获取任务队列的互斥锁 mtx_queue
。如果线程池已经停止,函数将抛出一个运行时异常。
然后,函数使用 emplace
方法将 lambda 表达式添加到任务队列中。这个 lambda 表达式实际上是通过 bind
将任务和参数绑定,并使用 (*task)()
执行任务。
最后,函数通过条件变量 cv
发送通知,告知线程池有新的任务可执行。并返回之前获取的任务结果的 future
对象。
总结:
这段代码的作用是将一个任务添加到线程池的任务队列中,并返回一个 future
对象,以便在需要时获取任务的执行结果。