本文基于C++实现一个简易线程池,线程池的创建归根结底即就是为了减少在一个工程中不断地创建和销毁线程所带来的开销。实现原理简单来讲分为两部分:
1.实现一个线程容器,里面存储一定量的线程。
2.实现一个任务队列,里面存储我们希望线程去执行的各种任务。
基于这两部分,此线程池的工作原理就是为任务队列中的任务分配线程容器中处于空闲状态的线程,下面是具体的代码展示:
线程池类声明
class threadpool
{
public:
threadpool(int threadnum);
template<typename T,typename ...Arg>
auto enque(T&& type,Arg&&... arg) -> std::future<typename std::result_of<T(Arg...)>::type>;
~threadpool();
private:
std::vector<std::thread> thread_vec;
std::queue<std::function<void()>> task_que;
std::mutex mymutex;
std::condition_variable cv;
void work();
bool isstop;
};
线程池构造
threadpool::threadpool(int threadnum) : isstop(false)
{
for (int i = 0; i < threadnum; i++)
{
thread_vec.emplace_back([this]() {
this->work();
});
}
}
这里对threadpool进行构造主要就是去创建你指定数量的线程,并将他们放入预先声明的一个线程容器中。需要注意isstop变量作用是管理线程池的开闭,故此时应该为false
线程池析构
threadpool::~threadpool()
{
{
std::unique_lock<std::mutex> lock(mymutex);
isstop = true;
}
cv.notify_all();
for (std::thread& t : thread_vec)
{
t.join();
}
}
threadpool进行析构时第一步就是改变isstop的值,注意此时要加一个锁,防止多个线程同时对isstop进行修改(这是一个很好的习惯),之后通知所有还在等待状态的线程,将他们阻塞到当前线程中,保证所有线程能在threadpool析构完成前执行完自己的工作
任务入队
template<typename T, typename ...Arg>
auto threadpool::enque(T&& type, Arg&&... arg) -> std::future<typename std::result_of<T(Arg...)>::type> {
using functype = typename std::result_of<T(Arg...)>::type;
auto task = std::make_shared<std::packaged_task<functype()>>(std::bind(std::forward<T>(type), std::forward<Arg>(arg)...));
std::future<functype> fut = task->get_future();
{
std::lock_guard<std::mutex> lock_enque(mymutex);
if (isstop)
{
throw std::runtime_error("出错:线程池已停止");
}
task_que.emplace([task]() {
(*task)();
});
}
cv.notify_one();
return fut;
}
由于我们需要执行的任务是多种多样的(我们以函数形式来代表任务),故我们应该将此任务入队函数写为一个模板函数,毕竟函数的返回类型和接收的参数形式我们不得而知。同时为方便操作我们将所有任务封装到一个智能指针中,同时为实现异步编程来提高开发性能我们还需要利用future类,之后就将封装好的任务放入任务队列中,注意此时的操作也一定是要加锁的
,任务执行
void threadpool::work() {
while (true)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mymutex);
cv.wait(lock, [this]() {return this->isstop || !this->task_que.empty(); });
if (isstop && task_que.empty())
{
return;
}
task = std::move(this->task_que.front());
this->task_que.pop();
}
task();
}
}
该函数无非就是从任务队列中取出满足要求的待处理任务,并执行他。需要注意的是由于涉及模板编程,故线程池中相应成员函数的编写应该放在hpp文件中
实现以上功能后便可以通过实例化线程池类来完成我们的需求
int main() {
threadpool pool(4);
for (int i = 0; i < 20; i++)
{
auto myfuture = pool.enque([](int a, int b) -> int {
std::cout << std::this_thread::get_id() << std::endl;
return a + b; }, 10 * i, 10 * i);
std::cout << "val = " << myfuture.get() << std::endl;
}
std::cin.get();
return 0;
}
参考自b站up主开发者LaoJ
以上是我的一些见解,望讨论。