当前位置: 首页 > news >正文

c++源码阅读__ThreadPool__正文阅读

一. 简介

本章我们开始阅读c++ git 高星开源项目ThreadPool, 这是一个纯c++的线程池项目, 并且代码量极小, 非常适合新手阅读
git地址: progschj / ThreadPool

二. 前提知识

为了面对不同读者对c++掌握情况不同的情况, 这里我会将基本上稍微值得一说的前提知识点, 全部专门写成一篇博客, 同学们在阅读本篇之前, 可以先去阅读前提知识部分
c++源码阅读__ThreadPool__前提基础部分
还有线程的一些基础知识
C++ 多线程 菜鸟教程

三. 源码:

因为源码时c++11的, 所以我们如果用最新的标准是跑不起来的, 所以这里我在下面源码部分把能用最新标准跑的版本的代码贴了出来
修改的地方只有一处, 如下

返回类型的推导: 
typename std::result_of<F(Args...)>::type
改为了
typename std::invoke_result<F, Args...>::type

由于此项目比较小, 所以我们直接把代码全部贴出来, 并且在代码中, 用注释附上讲解

#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>
#include <iostream>class ThreadPool {
public:ThreadPool(size_t);// 给线程池设置任务的方法, 核心逻辑template<class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::invoke_result<F, Args...>::type>;~ThreadPool();
private:// 线程队列的 vectorstd::vector< std::thread > workers;// 任务列表, 在前提知识里, 已经说明了, 通过lambda和bind将方法斗包装为void()类型的// 至于任务的返回值, 通过future实现std::queue< std::function<void()> > tasks;// synchronizationstd::mutex queue_mutex;// 主线程通知子线程的工具std::condition_variable condition;bool stop;
};// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads): stop(false)
{for (size_t i = 0; i < threads; ++i)// 这了workers.emplace_back([](){}) 为什么能够直接生成Thread呢?// 因为emplace_back 和 push_back不同, emplace_back传入的参数可以是 构造函数的 参数, // 所以这里写全了 应该是类似下面的代码// workers.emplace_back( Thread( [](){} ) )workers.emplace_back([this]{for (;;){std::function<void()> task;{// 这里单独{}开一个域, 是因为unique_lock生效的范围是当前作用域std::unique_lock<std::mutex> lock(this->queue_mutex);// 这里condition_variable的wait, 等待一个notifythis->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if (this->stop && this->tasks.empty()){std::cout << std::this_thread::get_id() << std::endl;return;}task = std::move(this->tasks.front());this->tasks.pop();}// 脱离了lock域, 真正执行方法的地方, 还是多线程的, 如果写在上面的lock域里// 那就变成 "单线程" 了task();}});
}// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) ->std::future<typename std::invoke_result<F, Args...>::type>
{using return_type = typename std::invoke_result<F, Args...>::type;// 为什么要用shared_ptr? 因为后面使用queue.emplace, 会将task传递到queue中,// 当离开此方法时, task因为离开作用域, 会销毁, 而shared_ptr则不会销毁, 而是引用计数-1auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));// 并且这里要注意, task不是packaged_task, 而是shared_ptrstd::future<return_type> res = task->get_future();{// 为什么这里要开一个单独的作用域呢? 因为这里unique_lock是以作用域进行lock的std::unique_lock<std::mutex> lock(queue_mutex);// don't allow enqueueing after stopping the poolif (stop)throw std::runtime_error("enqueue on stopped ThreadPool");// 这里通过lambda, 及那个task包装为一个void()的函数, 里面的*task是shared_ptr指针指向的packaged_tasktasks.emplace([task]() { (*task)(); });}// 任务装入queue后, 通知子线程执行condition.notify_one();return res;
}// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{{// 同样的原因, unique_lock的生效范围是当前作用域std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();// 执行到notify_all的时候, 在线程的方法里, 实际上已经return了, 这里循环join// 是为了让主线程结束的时候, 子线程也随之结束for (std::thread& worker : workers)worker.join();
}#endif

调用方法

#include "ThreadPool.h"
using namespace std;int main()
{// 线程数量为4的鲜橙汁ThreadPool pool(4);// 8个返回类型都是int的future数组std::vector< std::future<int> > results;for (int i = 0; i < 8; ++i) {auto f = [i]() {std::this_thread::sleep_for(std::chrono::seconds(1));return i;};// 设置任务, 并将返回的future放入results里results.emplace_back(pool.enqueue(f));}// 循环打印结果for (auto&& result : results)std::cout << result.get() << endl;// 设置一个string(const char*, const char*)的方法, 并获取返回的future<string>std::future<string> f = pool.enqueue([](const char* s1, const char* s2) {return string(s1) + s2;}, "hello ", "world");cout << f.get() << endl;std::cout << std::endl;return 0;
}

执行结果
在这里插入图片描述

总结

技巧:

  1. 通过lambda 或者 bind 来改变函数的参数个数
  2. 通过构造 packaged_task来改变返回值传递的方式, 方便将方法统一放入vector, 并且是异步执行的
  3. 通过lambda来改变函数返回值类型
http://www.lryc.cn/news/487817.html

相关文章:

  • 关于ES的查询
  • 数据结构初识
  • 保存数据到Oracle时报错ORA-17004: 列类型无效: 1111
  • Excel——宏教程(1)
  • 论文浅尝 | MindMap:知识图谱提示激发大型语言模型中的思维图(ACL2024)
  • 第6章:TDengine 标签索引和删除数据
  • 【微软:多模态基础模型】(5)多模态大模型:通过LLM训练
  • 海外带云仓多语言商城源码,多语言多商家云仓一键代发商城
  • android:taskAffinity 对Activity退出时跳转的影响
  • Apache Dolphinscheduler数据质量源码分析
  • solana链上智能合约开发案例一则
  • 使用 PyTorch 实现 ZFNet 进行 MNIST 图像分类
  • 车轮上的科技:Spring Boot汽车新闻集散地
  • IDEA2023 SpringBoot整合Web开发(二)
  • 国产三维CAD 2025新动向:推进MBD模式,联通企业设计-制造数据
  • ubuntu 之 安装mysql8
  • Flink Lookup Join(维表 Join)
  • Elasticsearch retrievers 通常与 Elasticsearch 8.16.0 一起正式发布!
  • 【并发模式】Go 常见并发模式实现Runner、Pool、Work
  • 【前端知识】Javascript前端框架Vue入门
  • Springboot3.3.5 启动流程之 Bean创建流程
  • golang反射函数注册
  • 【Spring】Bean
  • 深入解析TK技术下视频音频不同步的成因与解决方案
  • 为什么要使用Ansible实现Linux管理自动化?
  • Android:任意层级树形控件(有效果图和Demo示例)
  • C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
  • C++---类型转换
  • CSS基础学习练习题
  • TypeScript知识点总结和案例使用