// 话不多说,先上代码:
// 注意:本文代码来源于https://github.com/progschj/ThreadPool.git
// 不需要注释,想要干净代码的可以直接上GitHub去找资源
//头文件 线程池类代码
/*
* Date : 2018/7/24
* Author : wqw
* Content : ThreadPool 线程池实现
* Origin : https://github.com/progschj/ThreadPool.git
* File : ThreadPool.h
*/
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool{
public:
ThreadPool(size_t);
// auto 自动推导类型, Args... 不定形参列表, -> xxx 返回值类型
// 添加任务
template <class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
std::vector<std::thread> workers; // 执行任务的线程容器
// std::function<> 函数包装, 任务队列
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex; // 互斥锁
std::condition_variable condition; // 条件锁
bool stop; // 判断是否停止线程池
};
// 构造函数 开启一定数量的线程
inline ThreadPool::ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; i++) {
// 添加工作线程到workers
workers.emplace_back(
// lambda 匿名函数
[this] {
// 让线程持续工作,
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex); // 互斥锁开启,直到生命周期结束
// 若任务列表 tasks 为空,则暂时阻塞线程
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front()); // 将tasks最前面的以右值形式赋值给task
this->tasks.pop(); // 将tasks刚刚赋值给task的元素弹出
}
task(); // 让线程执行函数
}
}
);
}
}
// 添加工作
// 返回值为该工作(即将要执行的任务函数)的返回值
template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
// 自定义返回值类型 类似于typedef的用法
using return_type = typename std::result_of<F(Args...)>::type;
// forward() 保证完美转发,即参数左值右值类型不变
// std::packaged_task与std::promise相似 用于异步调用,常与std::future共同使用
// std::bind 包装器 对函数F<Args>进行包装
// make_shared 智能指针 制作一个智能指针指向包装过的F<Args>
auto task = std::make_shared<std::packaged_task<return_type()>> (
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
// std::future 并发编程 绑定task任务
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
// 判断ThreadPool是否停止
if (stop)
throw std::runtime_error("enqueue on stopped ThreadPool!\n");
// 添加任务到任务列表
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one(); // 唤醒一个任务
return res;
}
// 析构函数
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
stop = true;
}
condition.notify_all(); // 唤醒所有任务
for (std::thread &worker: workers)
worker.join(); // 线程自动加上互斥锁执行, detach则是并发执行
}
#endif
// example 测试代码
/*
* Date : 2018/7/24
* Author : wqw
* Content : ThreadPool 线程池实现类
* Origin : https://github.com/progschj/ThreadPool.git
* File : main.cpp
*/
#include <iostream>
#include <vector>
#include <chrono>
#include "Thread_pool.h"
int main()
{
ThreadPool pool(4);
std::vector< std::future<int> > results;
for(int i = 0; i < 8; ++i) {
results.emplace_back(
pool.enqueue([i] {
std::cout << "hello " << i << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "world " << i << std::endl;
return i*i;
})
);
}
for(auto && result: results)
std::cout << result.get() << ' ';
std::cout << std::endl;
return 0;
}
代码已经上了,接下来是个人理解。
线程池的概念:
线程池是一个类似于银行办理业务的排队系统一样的东西。先开一定数量的线程(如:银行业务窗口),若线程没任何任务则阻塞全部线程,但不关闭(如:银行暂时没人办理业务,但是工作人员总不能下班吧,还是要待机)。若需要执行的任务很多,则我们需要创建一个队列,让队列里的任务先等候,等到有线程空闲时则从队列中取任务(如:银行人很多就要排队一个一个来)。
该代码的线程池的思路:
这个线程池类主要由三大部分(三个成员函数)组成:
构造函数 ThreadPool():
开启一定数量的线程,每开启一个线程,让该线程进入死循环,对接下来的操作,先利用互斥锁先加锁,再利用条件锁判断线程任务列表是否为空,若为空即阻塞该线程(就是没人办理业务时进入待机状态)。接下来,从任务列表中取任务,然后解开互斥锁,让其执行完后再重复以上操作。对于开启的每一个线程都是如此。
添加任务函数 enqueue():
先获取任务,加互斥锁,将该任务加入任务列表,解锁,然后唤醒一个任务,让其进行等待。
析构函数 ~ThreadPool():
唤醒所有的工作,让线程一个个的执行。
C++ 新标准学习感悟:
最近自学线程池,上GitHub找了一个关注最多的代码,clone后发现自己看不懂代码。一堆没见过的函数和机制,我以为这就是大神级别的,我看不懂也很正常。后来,我一直研究这100行代码,发现C++11 多了很多新的标准,新东西,是自己太菜才看不懂(本人目前真的是个小白)。为了看懂这段代码我查阅了很多其中涉及的知识,以下是我发现的新知识及一些参考文章。
1. auto 自动推导类型
2. ... 可变模板参数 这篇文章有关其概念使用方法及其参数展开 https://www.cnblogs.com/qicosmos/p/4325949.html
3. function,bind 等是函数包装器,这两个有类似之处都是对函数进行包装
使用方法部分简介可以参考该文章 https://blog.youkuaiyun.com/liukang325/article/details/53668046
4. mutex 锁类 详情自行查阅api
5. [] { ... } lambda 匿名函数表达式 此文章有详细的介绍 https://www.cnblogs.com/DswCnblog/p/5629165.html
6. &&,move(),forward() 这里的&&不是指 且 的意思,是右值引用。这里涉及一个左值右值的问题,move()和forward() ,就是围绕左值引用,右值引用而设立的专门的函数。是一个很麻烦但是又挺重要的知识点。
这个文章写的比较通俗易懂 http://www.cnblogs.com/qicosmos/p/4283455.html#3995775
这篇文章是更深入的讲解 http://www.cnblogs.com/catch/p/3507883.html (这篇我都看到迷迷糊糊^.^)
7. future, promise, package_task 这几个是关于线程方面的辅助函数,future与(promise,package)配合使用,用于异步读取,追踪,正在的线程中某些变量。这篇文章有其概念的简介及联系 https://blog.youkuaiyun.com/jiange_zh/article/details/51602938
8. make_share 用于制作智能指针 share_ptr C++ primer 6 有介绍。该智能指针是会自动释放内存故比较安全。
该文章有其简单概述及使用 https://blog.youkuaiyun.com/yagerfgcs/article/details/72886630
以上均为个人理解,欢迎提出建议或指出错误!
本文如有侵犯他人版权可在评论区指出,或邮件提示AuthorityWu@outlook.com