• 文件浏览器
  • 000 《Folly 库知识框架》 001 《folly::Utility 权威指南》 002 《folly::Preprocessor 权威指南》 003 《Folly::Traits 权威指南:C++ 元编程的基石》 004 《Folly::ScopeGuard 权威指南:C++ 作用域资源管理利器》 005 《Folly Singleton 权威指南:从入门到精通》 007 《Folly Dynamic.h 权威指南:C++ 动态类型实战》 008 《Folly Optional.h 权威指南:从入门到精通》 009 《Folly Expected.h 权威指南》 010 《Folly Try.h 权威指南:C++ 异常处理的现代实践》 011 《Folly Variant.h 权威指南》 012 《folly::Vector 权威指南: 深度探索与实践》 013 《Folly Map 权威指南:从入门到精通》 014 《Folly Set 权威指南》 015 《Folly SmallVector 权威指南》 016 《Folly Allocator.h 权威指南:C++ 高性能内存管理深度解析》 017 《Folly Foreach.h 权威指南:从入门到精通》 018 《folly/futures 权威指南:Future 和 Promise 深度解析与实战》 019 《Folly Executor 权威指南:从入门到精通 (Folly Executor: The Definitive Guide from Beginner to Expert)》 020 《深入浅出 Folly Fibers:FiberManager 和 Fiber 权威指南》 021 《folly EventBase.h 编程权威指南》 022 《Folly Baton.h 权威指南:C++ 高效线程同步实战》 023 《深入探索 folly/Synchronized.h:并发编程的基石 (In-depth Exploration of folly/Synchronized.h: The Cornerstone of Concurrent Programming)》 024 《folly/SpinLock.h 权威指南:原理、应用与最佳实践》 025 《Folly SharedMutex.h 权威指南:原理、应用与实战》 026 《Folly AtomicHashMap.h 权威指南:从入门到精通》 027 《Folly/IO 权威指南:高效网络编程实战》 028 《folly/Uri.h 权威指南 (Folly/Uri.h: The Definitive Guide)》 029 《Folly String.h 权威指南:深度解析、实战应用与高级技巧》 030 《folly/Format.h 权威指南 (The Definitive Guide to folly/Format.h)》 031 《Folly Conv.h 权威指南:C++ 高效类型转换详解》 032 《folly/Unicode.h 权威指南:深入探索与实战应用》 033 《folly/json.h 权威指南》 034 《Folly Regex.h 权威指南:从入门到精通 (Folly Regex.h: The Definitive Guide from Beginner to Expert)》 035 《Folly Clock.h 权威指南:系统、实战与深度解析》 036 《folly/Time.h 权威指南:C++ 时间编程实战》 037 《Folly Chrono.h 权威指南》 038 《Folly ThreadName.h 权威指南:系统线程命名深度解析与实战》 039 《Folly OptionParser.h 权威指南》 040 《C++ Range.h 实战指南:从入门到专家》 041 《Folly File.h 权威指南:从入门到精通》 042 《Folly/xlog.h 权威指南:从入门到精通》 043 《Folly Trace.h 权威指南:从入门到精通 (Folly Trace.h: The Definitive Guide from Beginner to Expert)》 044 《Folly Demangle.h 权威指南:C++ 符号反解的艺术与实践 (Folly Demangle.h: The Definitive Guide to C++ Symbol Demangling)》 045 《folly/StackTrace.h 权威指南:原理、应用与最佳实践 (folly/StackTrace.h Definitive Guide: Principles, Applications, and Best Practices)》 046 《Folly Test.h 权威指南:C++ 单元测试实战 (Folly Test.h: The Definitive Guide to C++ Unit Testing in Practice)》 047 《《Folly Benchmark.h 权威指南 (Folly Benchmark.h: The Definitive Guide)》》 048 《Folly Random.h 权威指南:C++随机数生成深度解析》 049 《Folly Numeric.h 权威指南》 050 《Folly Math.h 权威指南:从入门到精通 (Folly Math.h: The Definitive Guide from Beginner to Expert)》 051 《Folly FBMath.h 权威指南:从入门到精通 (Folly FBMath.h: The Definitive Guide - From Beginner to Expert)》 052 《Folly Cursor.h 权威指南:高效数据读取与解析 (Folly Cursor.h Authoritative Guide: Efficient Data Reading and Parsing)》 053 《Folly与Facebook Thrift权威指南:从入门到精通 (Folly and Facebook Thrift: The Definitive Guide from Beginner to Expert)》 054 《Folly CPUThreadPoolExecutor.h 权威指南:原理、实践与高级应用》 055 《Folly HardwareConcurrency.h 权威指南:系统级并发编程基石》

    019 《Folly Executor 权威指南:从入门到精通 (Folly Executor: The Definitive Guide from Beginner to Expert)》


    作者Lou Xiao, gemini创建时间2025-04-17 01:48:23更新时间2025-04-17 01:48:23

    🌟🌟🌟本文案由Gemini 2.0 Flash Thinking Experimental 01-21创作,用来辅助学习知识。🌟🌟🌟

    书籍大纲

    ▮▮▮▮ 1. chapter 1: 走进并发世界 (Introduction to Concurrency)
    ▮▮▮▮▮▮▮ 1.1 什么是并发与并行 (What are Concurrency and Parallelism)
    ▮▮▮▮▮▮▮ 1.2 为什么需要并发 (Why Concurrency Matters)
    ▮▮▮▮▮▮▮ 1.3 线程、进程与协程 (Threads, Processes, and Coroutines)
    ▮▮▮▮▮▮▮ 1.4 并发编程的挑战与陷阱 (Challenges and Pitfalls in Concurrent Programming)
    ▮▮▮▮▮▮▮▮▮▮▮ 1.4.1 竞态条件 (Race Conditions)
    ▮▮▮▮▮▮▮▮▮▮▮ 1.4.2 死锁 (Deadlocks)
    ▮▮▮▮▮▮▮▮▮▮▮ 1.4.3 活锁 (Livelocks)
    ▮▮▮▮▮▮▮▮▮▮▮ 1.4.4 饥饿 (Starvation)
    ▮▮▮▮ 2. chapter 2: Executor 模式:并发的基石 (Executor Pattern: The Cornerstone of Concurrency)
    ▮▮▮▮▮▮▮ 2.1 Executor 模式的设计思想 (Design Philosophy of Executor Pattern)
    ▮▮▮▮▮▮▮ 2.2 Executor 模式的优势与应用场景 (Advantages and Application Scenarios of Executor Pattern)
    ▮▮▮▮▮▮▮ 2.3 从 std::threadExecutor:演进之路 (From std::thread to Executor: The Evolution)
    ▮▮▮▮▮▮▮ 2.4 常见的 Executor 类型 (Common Executor Types)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.1 线程池 (Thread Pool)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.2 单线程 Executor (Single-Thread Executor)
    ▮▮▮▮▮▮▮▮▮▮▮ 2.4.3 Fork-Join Executor (Fork-Join Executor)
    ▮▮▮▮ 3. chapter 3: Folly Executor 概览 (Overview of Folly Executor)
    ▮▮▮▮▮▮▮ 3.1 Folly 库简介 (Introduction to Folly Library)
    ▮▮▮▮▮▮▮ 3.2 为什么选择 Folly Executor (Why Choose Folly Executor)
    ▮▮▮▮▮▮▮ 3.3 Folly Executor 的核心组件 (Core Components of Folly Executor)
    ▮▮▮▮▮▮▮ 3.4 Folly Executor 的设计原则 (Design Principles of Folly Executor)
    ▮▮▮▮ 4. chapter 4: folly::Executor 接口详解 (Detailed Explanation of folly::Executor Interface)
    ▮▮▮▮▮▮▮ 4.1 folly::Executor 的基本接口定义 (Basic Interface Definition of folly::Executor)
    ▮▮▮▮▮▮▮ 4.2 execute() 方法:任务提交的核心 (The execute() Method: Core of Task Submission)
    ▮▮▮▮▮▮▮ 4.3 schedule() 方法:延迟任务执行 (The schedule() Method: Delayed Task Execution)
    ▮▮▮▮▮▮▮ 4.4 getKeepAliveToken() 方法:管理 Executor 生命周期 (The getKeepAliveToken() Method: Managing Executor Lifecycle)
    ▮▮▮▮▮▮▮ 4.5 ~Executor() 析构函数:Executor 的清理 (The ~Executor() Destructor: Executor Cleanup)
    ▮▮▮▮ 5. chapter 5: 常用 Folly Executor 实现 (Common Folly Executor Implementations)
    ▮▮▮▮▮▮▮ 5.1 ThreadPoolExecutor:强大的线程池实现 ( ThreadPoolExecutor: Powerful Thread Pool Implementation)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.1.1 ThreadPoolExecutor 的构造与配置 (Construction and Configuration of ThreadPoolExecutor)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.1.2 ThreadPoolExecutor 的任务队列与调度策略 (Task Queue and Scheduling Strategy of ThreadPoolExecutor)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.1.3 ThreadPoolExecutor 的线程管理与回收 (Thread Management and Recycling of ThreadPoolExecutor)
    ▮▮▮▮▮▮▮ 5.2 InlineExecutor:同步执行的利器 ( InlineExecutor: Synchronous Execution Tool)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.2.1 InlineExecutor 的工作原理与适用场景 (Working Principle and Applicable Scenarios of InlineExecutor)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.2.2 InlineExecutor 的代码示例与最佳实践 (Code Examples and Best Practices of InlineExecutor)
    ▮▮▮▮▮▮▮ 5.3 IOExecutor:专为 I/O 密集型任务设计 ( IOExecutor: Designed for I/O-Intensive Tasks)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.3.1 IOExecutor 的特性与优势 (Features and Advantages of IOExecutor)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.3.2 IOExecutor 在网络编程中的应用 (Application of IOExecutor in Network Programming)
    ▮▮▮▮▮▮▮ 5.4 CPUThreadPoolExecutor:针对 CPU 密集型任务优化 ( CPUThreadPoolExecutor: Optimized for CPU-Intensive Tasks)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.4.1 CPUThreadPoolExecutor 的设计考量 (Design Considerations of CPUThreadPoolExecutor)
    ▮▮▮▮▮▮▮▮▮▮▮ 5.4.2 CPUThreadPoolExecutorThreadPoolExecutor 的对比 (Comparison between CPUThreadPoolExecutor and ThreadPoolExecutor)
    ▮▮▮▮ 6. chapter 6: 任务提交与管理 (Task Submission and Management)
    ▮▮▮▮▮▮▮ 6.1 使用 Lambda 表达式提交任务 (Submitting Tasks using Lambda Expressions)
    ▮▮▮▮▮▮▮ 6.2 使用 Functor 提交任务 (Submitting Tasks using Functors)
    ▮▮▮▮▮▮▮ 6.3 任务的取消与中断 (Task Cancellation and Interruption)
    ▮▮▮▮▮▮▮ 6.4 任务的优先级管理 (Task Priority Management)
    ▮▮▮▮ 7. chapter 7: Executor 的高级应用 (Advanced Applications of Executor)
    ▮▮▮▮▮▮▮ 7.1 自定义 Executor 的实现 (Implementing Custom Executors)
    ▮▮▮▮▮▮▮▮▮▮▮ 7.1.1 自定义 Executor 的接口设计 (Interface Design of Custom Executors)
    ▮▮▮▮▮▮▮▮▮▮▮ 7.1.2 实现一个简单的 FIFO Executor (Implementing a Simple FIFO Executor)
    ▮▮▮▮▮▮▮ 7.2 Executor 的组合与链式调用 (Composition and Chaining of Executors)
    ▮▮▮▮▮▮▮ 7.3 Executor 与 Future/Promise 的协同工作 (Collaboration of Executor with Future/Promise)
    ▮▮▮▮▮▮▮ 7.4 Executor 在异步编程中的应用 (Application of Executor in Asynchronous Programming)
    ▮▮▮▮ 8. chapter 8: Executor 的性能调优与监控 (Performance Tuning and Monitoring of Executor)
    ▮▮▮▮▮▮▮ 8.1 Executor 的性能指标 (Performance Metrics of Executor)
    ▮▮▮▮▮▮▮ 8.2 线程池大小的动态调整 (Dynamic Adjustment of Thread Pool Size)
    ▮▮▮▮▮▮▮ 8.3 任务队列的选择与优化 (Selection and Optimization of Task Queue)
    ▮▮▮▮▮▮▮ 8.4 使用 Folly PerfCounter 进行性能监控 (Performance Monitoring using Folly PerfCounter)
    ▮▮▮▮ 9. chapter 9: 实战案例分析 (Practical Case Study Analysis)
    ▮▮▮▮▮▮▮ 9.1 案例一:构建高并发网络服务器 (Case Study 1: Building a High-Concurrency Network Server)
    ▮▮▮▮▮▮▮▮▮▮▮ 9.1.1 使用 IOExecutor 处理网络 I/O (Using IOExecutor to Handle Network I/O)
    ▮▮▮▮▮▮▮▮▮▮▮ 9.1.2 使用 CPUThreadPoolExecutor 处理业务逻辑 (Using CPUThreadPoolExecutor to Handle Business Logic)
    ▮▮▮▮▮▮▮ 9.2 案例二:实现高效的数据处理流水线 (Case Study 2: Implementing an Efficient Data Processing Pipeline)
    ▮▮▮▮▮▮▮▮▮▮▮ 9.2.1 使用 Executor 构建数据处理阶段 (Using Executor to Build Data Processing Stages)
    ▮▮▮▮▮▮▮▮▮▮▮ 9.2.2 Executor 的选择与流水线性能优化 (Executor Selection and Pipeline Performance Optimization)
    ▮▮▮▮ 10. chapter 10: Folly Executor API 全面解析 (Comprehensive API Analysis of Folly Executor)
    ▮▮▮▮▮▮▮ 10.1 folly::Executor 类 API 详解 (Detailed API Explanation of folly::Executor Class)
    ▮▮▮▮▮▮▮ 10.2 folly::ThreadPoolExecutor 类 API 详解 (Detailed API Explanation of folly::ThreadPoolExecutor Class)
    ▮▮▮▮▮▮▮ 10.3 folly::InlineExecutor 类 API 详解 (Detailed API Explanation of folly::InlineExecutor Class)
    ▮▮▮▮▮▮▮ 10.4 folly::IOExecutor 类 API 详解 (Detailed API Explanation of folly::IOExecutor Class)
    ▮▮▮▮▮▮▮ 10.5 folly::CPUThreadPoolExecutor 类 API 详解 (Detailed API Explanation of folly::CPUThreadPoolExecutor Class)
    ▮▮▮▮ 11. chapter 11: 总结与展望 (Summary and Future Outlook)
    ▮▮▮▮▮▮▮ 11.1 Folly Executor 的优势与局限性 (Advantages and Limitations of Folly Executor)
    ▮▮▮▮▮▮▮ 11.2 未来并发编程技术的发展趋势 (Development Trends in Future Concurrent Programming Technologies)
    ▮▮▮▮▮▮▮ 11.3 持续学习与深入探索 (Continuous Learning and In-depth Exploration)


    1. chapter 1: 走进并发世界 (Introduction to Concurrency)

    1.1 什么是并发与并行 (What are Concurrency and Parallelism)

    在当今快速发展的计算机技术领域,并发(Concurrency)并行(Parallelism) 已成为构建高效、响应迅速的应用程序的核心概念。虽然这两个术语经常被放在一起讨论,甚至有时会被混用,但它们在本质上代表着不同的概念,理解它们之间的区别对于掌握并发编程至关重要。

    并发(Concurrency) 指的是程序同时处理多个任务的能力,但这并不意味着这些任务在同一时刻被执行。更准确地说,并发是一种程序结构,它允许程序在逻辑上被分解为多个独立的执行单元,这些单元可以交替执行,从而在宏观上看起来像是同时在进行。想象一下,一位单核 CPU 的厨师在准备晚餐:他可能需要同时处理切菜、煮饭和炒菜等多项任务。虽然他不能在同一时刻做所有事情,但他可以通过快速地在不同任务之间切换,例如,切一会儿菜,然后去看看锅里的饭,再回来炒菜,最终完成所有菜肴的烹饪。这种快速切换执行不同任务的能力,就类似于并发。在计算机科学中,这种快速切换通常由操作系统或运行时环境来管理,例如通过时间片轮转等机制,使得用户感觉程序中的多个任务在“同时”进行。

    并行(Parallelism) 则指的是程序在同一时刻执行多个任务的能力。这通常需要多核处理器分布式系统的支持,使得不同的任务可以被分配到不同的处理单元上,真正地同时执行。继续用厨师的例子来类比,如果现在有多位厨师(例如双核 CPU 或多核 CPU),他们可以同时进行切菜、煮饭和炒菜等不同的烹饪任务,从而显著缩短晚餐的准备时间。在计算机领域,并行计算能够充分利用硬件资源,显著提高程序的执行效率,尤其是在处理计算密集型任务时,优势更加明显。

    为了更清晰地理解并发与并行之间的区别,我们可以用一个更贴切的比喻:

    并发:就像单车道的道路上,车辆交替通行。虽然车辆看起来都在向前移动,但在任何一个时刻,只有一个方向的车辆能够通行。
    并行:就像多车道的道路上,多个方向的车辆可以同时通行,互不干扰,效率更高。

    特征并发(Concurrency)并行(Parallelism)
    核心概念程序结构,关注如何组织和管理任务,使其看起来像同时进行执行方式,关注如何真正地同时执行多个任务
    执行方式任务交替执行,快速切换任务同时执行,真正意义上的同步进行
    硬件需求单核或多核 CPU 均可通常需要多核 CPU 或分布式系统支持
    目标提高程序的响应性资源利用率(例如 I/O 操作期间可以执行其他任务)提高程序的执行速度,缩短任务完成时间
    任务关系任务之间可能存在依赖关系,需要协调和同步任务之间可以相互独立,也可以存在依赖关系,但更侧重于同时执行
    适用场景I/O 密集型任务、需要快速响应的任务、资源受限的环境CPU 密集型任务、需要高性能计算的任务、资源充足的环境
    关注点如何有效地调度切换任务如何有效地分配利用计算资源,实现真正的同步执行

    总结来说,并发是关于如何更好地组织程序代码,以便能够处理多个任务,即使在单核处理器上也能提高效率和响应性;而并行是关于如何利用硬件资源来真正地同时执行多个任务,以达到更高的性能。并发是并行的基础,并行是并发的一种实现方式。一个良好的并发设计可以更容易地实现并行化,从而充分发挥多核处理器的性能优势。在实际应用中,我们常常需要结合使用并发和并行技术,以构建既高效又响应迅速的应用程序。

    1.2 为什么需要并发 (Why Concurrency Matters)

    在计算机发展的早期阶段,单核处理器是主流,程序通常以顺序执行的方式运行,即一个任务完成后才能开始下一个任务。然而,随着计算机技术的飞速发展,以及应用场景的日益复杂,并发(Concurrency) 逐渐成为现代软件开发中不可或缺的关键技术。那么,为什么我们需要并发呢?

    提高程序响应性(Responsiveness)

    在用户与应用程序交互的过程中,用户通常期望应用程序能够快速响应用户的操作,而不是长时间的等待。例如,在图形用户界面(GUI)应用程序中,如果一个耗时的操作(例如文件下载、复杂计算等)在主线程中执行,会导致界面卡顿,用户体验非常糟糕。而通过引入并发,我们可以将这些耗时的操作放到后台线程中执行,主线程仍然可以保持响应,及时处理用户的输入和界面更新,从而显著提升用户体验。

    例如,在 Web 浏览器中,当我们浏览网页时,浏览器需要同时处理多个任务:

    下载网页内容:从服务器下载 HTML、CSS、JavaScript、图片等资源。
    渲染网页:解析 HTML 和 CSS,构建 DOM 树和渲染树,进行布局和绘制。
    执行 JavaScript 代码:处理用户交互、动态效果等。

    如果浏览器采用单线程模型,那么在下载大文件或者执行复杂的 JavaScript 代码时,整个浏览器界面可能会卡死,无法响应用户的操作。而现代浏览器都采用了多进程多线程的并发模型,将不同的任务分配到不同的执行单元中,即使某个任务被阻塞,也不会影响其他任务的执行,从而保证了浏览器的流畅性和响应性。

    充分利用多核处理器资源(Resource Utilization)

    随着多核处理器的普及,单核处理器的性能提升逐渐放缓,而多核处理器成为了提高计算能力的主要方向。然而,传统的单线程程序只能在一个 CPU 核心上运行,无法充分利用多核处理器的计算能力,造成了硬件资源的浪费。通过引入并发,我们可以将程序中的任务分解成多个可以并行执行的子任务,并将这些子任务分配到不同的 CPU 核心上,从而充分利用多核处理器的并行计算能力,显著提高程序的执行效率。

    例如,在图像处理、科学计算、大数据分析等领域,通常需要处理大量的数据和复杂的计算,单线程程序可能需要很长时间才能完成任务。而通过将计算任务并行化,例如将数据分割成多个部分,分配到不同的核心上同时处理,可以大幅缩短计算时间,提高处理效率。

    更好地处理 I/O 密集型任务(I/O-Bound Tasks)

    在计算机程序中,任务通常可以分为 CPU 密集型(CPU-Bound)I/O 密集型(I/O-Bound) 两种类型。CPU 密集型任务 指的是计算量大,需要消耗大量 CPU 资源的任务,例如复杂的数学计算、图像处理、视频编码等。I/O 密集型任务 指的是需要频繁进行 I/O 操作(例如磁盘读写、网络通信等)的任务,例如文件读写、网络请求、数据库操作等。

    对于 I/O 密集型任务,程序的执行时间主要花费在等待 I/O 操作完成上,CPU 的利用率通常不高。在单线程程序中,当程序发起一个 I/O 操作后,会阻塞等待 I/O 操作完成,CPU 会处于空闲状态,造成资源浪费。而通过引入并发,我们可以在等待 I/O 操作完成的过程中,让 CPU 去执行其他的任务,例如处理其他的 I/O 请求或者进行一些计算,从而提高 CPU 的利用率和程序的整体吞吐量。

    例如,在网络服务器中,服务器需要同时处理来自多个客户端的请求,每个请求都涉及到网络 I/O 操作。如果服务器采用单线程模型,那么在处理一个请求的 I/O 操作时,服务器将无法处理其他的请求,导致服务器的并发处理能力非常有限。而通过使用多线程异步 I/O 等并发技术,服务器可以同时处理多个客户端的请求,提高服务器的并发处理能力和吞吐量。

    简化复杂程序的建模(Simplified Modeling of Complex Systems)

    在某些情况下,使用并发可以更自然、更简洁地对复杂系统进行建模和设计。例如,在模拟现实世界中的并发事件(例如交通信号灯控制系统、银行交易系统等)时,使用并发编程可以更直观地反映系统的并发特性,使得程序结构更清晰、易于理解和维护。

    例如,在一个模拟交通信号灯控制系统的程序中,我们可以为每个信号灯创建一个独立的线程协程,每个执行单元负责控制一个信号灯的状态切换。这种并发模型更贴近现实世界的场景,使得程序的设计和实现更加自然和直观。

    综上所述,并发在现代软件开发中扮演着至关重要的角色,它可以提高程序的响应性、充分利用多核处理器资源、更好地处理 I/O 密集型任务,并简化复杂程序的建模。随着计算机技术的不断发展,以及应用场景的日益复杂,并发编程的重要性将越来越凸显。掌握并发编程技术,对于开发高性能、高可靠性、高响应性的应用程序至关重要。

    1.3 线程、进程与协程 (Threads, Processes, and Coroutines)

    在并发编程领域,线程(Threads)进程(Processes)协程(Coroutines) 是三个核心概念,它们都是实现并发的不同方式,各有特点和适用场景。理解它们之间的区别和联系,有助于我们选择合适的并发模型来解决实际问题。

    进程(Processes)

    进程 是操作系统进行资源分配和调度的基本单位,是程序的一次执行实例。每个进程都拥有独立的内存空间系统资源(例如文件句柄、网络连接等)。进程之间相互独立,一个进程的崩溃通常不会影响其他进程。进程间的通信(IPC,Inter-Process Communication)需要借助操作系统提供的机制,例如管道、消息队列、共享内存、套接字等,通信开销相对较大。

    特点

    资源隔离性好:进程之间拥有独立的内存空间,相互隔离,安全性高,稳定性好。
    上下文切换开销大:进程切换需要保存和恢复大量的上下文信息(例如内存映射、寄存器状态等),开销较大。
    进程间通信开销大:进程间通信需要借助操作系统提供的 IPC 机制,效率相对较低。
    创建和销毁开销大:创建和销毁进程需要分配和释放大量的系统资源,开销较大。

    适用场景

    需要高隔离性和稳定性的场景:例如操作系统、大型服务器程序等。
    CPU 密集型任务:可以利用多进程实现并行计算,充分利用多核处理器资源。
    容易崩溃的任务:将容易崩溃的任务放在独立的进程中,可以避免影响整个系统的稳定性。

    线程(Threads)

    线程 是进程内部的轻量级执行单元,是操作系统进行CPU 调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。线程间的通信非常方便,可以直接访问共享内存,但同时也带来了数据竞争同步的问题。线程的创建、销毁和切换开销比进程小得多,因此线程也被称为轻量级进程(Lightweight Processes)

    特点

    资源共享性好:线程之间共享进程的内存空间,通信方便高效。
    上下文切换开销小:线程切换只需要保存和恢复少量的上下文信息(例如寄存器状态、栈指针等),开销较小。
    线程间通信开销小:线程间可以直接访问共享内存,通信效率高。
    创建和销毁开销小:创建和销毁线程只需要分配和释放少量的系统资源,开销较小。
    资源隔离性差:线程之间共享进程的内存空间,一个线程的错误操作可能影响其他线程甚至整个进程的稳定性。
    需要同步机制:由于线程共享内存,需要使用锁、信号量等同步机制来避免数据竞争和保证数据一致性。

    适用场景

    I/O 密集型任务:可以利用多线程在等待 I/O 操作时执行其他任务,提高 CPU 利用率。
    需要频繁创建和销毁的任务:线程的创建和销毁开销较小,适合处理大量短生命周期的任务。
    需要共享数据的任务:线程之间共享内存,方便数据共享和通信。
    GUI 应用程序:可以使用多线程来保持界面的响应性,例如将耗时操作放在后台线程执行。

    协程(Coroutines)

    协程 是一种比线程更轻量级的并发机制,也被称为用户级线程(User-Level Threads)纤程(Fibers)。协程的调度完全由用户程序控制,而不是操作系统内核。一个线程可以包含多个协程,协程之间的切换发生在用户态,无需陷入内核,因此切换开销非常小。协程通常用于实现异步 I/O事件驱动编程等高性能并发模型。

    特点

    极致的轻量级:协程的创建、销毁和切换开销非常小,几乎可以忽略不计。
    用户态调度:协程的调度由用户程序控制,无需内核参与,避免了内核态切换的开销。
    更高的并发度:一个线程可以支持大量的协程,实现更高的并发度。
    更低的资源消耗:协程占用的资源比线程更少,可以降低系统资源消耗。
    编程模型相对复杂:协程的编程模型通常基于异步编程事件循环,需要一定的学习成本。
    无法利用多核并行:协程本质上是单线程的,无法直接利用多核处理器的并行计算能力。

    适用场景

    高并发 I/O 密集型任务:例如网络服务器、消息队列、游戏服务器等,需要处理大量的并发连接和 I/O 操作。
    异步编程:协程是实现异步编程的理想选择,可以简化异步代码的编写和维护。
    事件驱动编程:协程可以与事件循环结合使用,构建高效的事件驱动系统。
    需要极致性能和低延迟的场景:例如高性能网络应用、实时系统等。

    特征进程(Processes)线程(Threads)协程(Coroutines)
    资源隔离性弱(共享线程资源)
    上下文切换开销极小
    通信开销大(IPC)小(共享内存)小(共享内存或更轻量级的机制)
    创建/销毁开销极小
    调度操作系统内核调度操作系统内核调度用户程序调度
    并发度受系统资源限制,相对较低受系统资源限制,相对较高极高,一个线程可以支持数百万协程
    并行性可以利用多核并行可以利用多核并行本质上是单线程的,无法直接利用多核并行
    编程模型相对简单相对简单,但需要考虑同步问题相对复杂,通常基于异步编程或事件循环
    适用场景高隔离性、CPU 密集型、稳定性要求高的场景I/O 密集型、需要共享数据、GUI 应用等高并发 I/O 密集型、异步编程、事件驱动、极致性能要求的场景

    总结来说,进程、线程和协程都是实现并发的不同方式,它们在资源隔离性、上下文切换开销、通信开销、并发度、并行性等方面各有特点。选择哪种并发模型取决于具体的应用场景和需求。在实际应用中,我们常常需要根据任务的特性和性能需求,选择合适的并发模型,甚至将它们结合起来使用,以构建高效、可靠的并发程序。例如,可以使用多进程 + 多线程 的混合模型,利用进程的隔离性来提高稳定性,利用线程的共享性来方便通信,同时利用多核处理器实现并行计算。对于 I/O 密集型任务,可以使用线程池协程来提高并发处理能力和资源利用率。

    1.4 并发编程的挑战与陷阱 (Challenges and Pitfalls in Concurrent Programming)

    并发编程虽然能够带来诸多好处,例如提高程序响应性、充分利用硬件资源等,但同时也引入了新的复杂性和挑战。由于多个执行单元(线程、进程、协程等)共享资源相互协作,并发程序容易出现各种难以调试和排查的错误。理解并发编程的常见挑战和陷阱,有助于我们编写更健壮、更可靠的并发程序。

    1.4.1 竞态条件 (Race Conditions)

    竞态条件(Race Condition) 是指程序的执行结果依赖于多个线程(或进程、协程)执行的相对顺序,而这种执行顺序是不确定的,可能导致程序出现意外的错误或不一致的状态。当多个线程同时访问和修改共享资源,并且没有进行适当的同步控制时,就可能发生竞态条件。

    经典案例:银行账户余额更新

    假设有一个银行账户,初始余额为 100 元。现在有两个线程 A 和线程 B 同时对该账户进行操作:线程 A 执行存款操作,存入 50 元;线程 B 执行取款操作,取出 20 元。理想情况下,账户的最终余额应该是 100 + 50 - 20 = 130 元。

    然而,如果没有进行适当的同步控制,例如使用互斥锁(Mutex),就可能发生以下情况:

    1. 线程 A 读取账户余额(100 元)。
    2. 线程 B 也读取账户余额(100 元)。
    3. 线程 A 计算存款后的余额(100 + 50 = 150 元)。
    4. 线程 B 计算取款后的余额(100 - 20 = 80 元)。
    5. 线程 A 将计算结果 150 元写回账户余额。
    6. 线程 B 将计算结果 80 元写回账户余额。

    由于线程 B 的写操作发生在线程 A 的写操作之后,最终账户余额被线程 B 的结果覆盖,变成了 80 元,而不是预期的 130 元。这就是一个典型的竞态条件,由于多个线程对共享资源的访问顺序不确定,导致程序结果错误。

    避免竞态条件的常用方法

    互斥锁(Mutex):使用互斥锁来保护共享资源,保证在同一时刻只有一个线程可以访问共享资源,从而避免竞态条件。
    原子操作(Atomic Operations):使用原子操作来对共享资源进行操作,原子操作是不可分割的操作,可以保证操作的完整性,避免竞态条件。
    避免共享可变状态:尽量减少共享可变状态的使用,如果必须共享,则需要进行严格的同步控制。
    使用线程安全的数据结构:使用线程安全的数据结构(例如线程安全的队列、线程安全的哈希表等),这些数据结构内部已经实现了同步机制,可以避免竞态条件。

    1.4.2 死锁 (Deadlocks)

    死锁(Deadlock) 是指两个或多个线程(或进程)因互相等待对方释放资源而无限期地阻塞,导致程序无法继续执行的一种状态。死锁通常发生在多个线程竞争多个共享资源,并且请求资源的顺序不一致的情况下。

    经典案例:哲学家进餐问题

    哲学家进餐问题是计算机科学中一个经典的并发问题,可以用来演示死锁的发生。假设有五位哲学家围坐在一张圆桌旁,每两位哲学家之间都放着一根筷子,总共有五根筷子。哲学家们思考人生和享用美食,当哲学家想用餐时,必须同时拿起左右两边的筷子才能进餐。吃完饭后,哲学家会放下筷子继续思考。

    如果哲学家们都按照以下步骤操作:

    1. 拿起左边的筷子。
    2. 等待右边的筷子可用。
    3. 拿起右边的筷子。
    4. 进餐。
    5. 放下左右两边的筷子。

    那么,就可能发生死锁:

    1. 哲学家 1 拿起左边的筷子(筷子 1)。
    2. 哲学家 2 拿起左边的筷子(筷子 2)。
    3. 哲学家 3 拿起左边的筷子(筷子 3)。
    4. 哲学家 4 拿起左边的筷子(筷子 4)。
    5. 哲学家 5 拿起左边的筷子(筷子 5)。

    此时,所有哲学家都拿起了左边的筷子,但都无法拿起右边的筷子,因为右边的筷子已经被相邻的哲学家拿走了。所有哲学家都在等待对方释放筷子,导致互相等待,陷入死锁状态,谁也无法进餐。

    死锁发生的四个必要条件

    1. 互斥条件(Mutual Exclusion):资源在同一时刻只能被一个线程占用。
    2. 请求与保持条件(Hold and Wait):线程在持有至少一个资源的同时,又请求新的资源,而新资源被其他线程占用。
    3. 不可剥夺条件(No Preemption):线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺,只能由持有资源的线程主动释放。
    4. 循环等待条件(Circular Wait):存在一个线程等待资源的环路,例如线程 A 等待线程 B 占用的资源,线程 B 等待线程 C 占用的资源,...,线程 Z 等待线程 A 占用的资源。

    避免死锁的常用方法

    破坏死锁发生的必要条件:例如破坏循环等待条件,可以通过资源排序,让所有线程都按照相同的顺序请求资源,从而避免循环等待。
    资源预分配:在线程开始执行之前,一次性申请所有需要的资源,如果资源不足,则线程阻塞等待,直到所有资源都可用。
    死锁检测与恢复:定期检测系统中是否发生死锁,如果检测到死锁,则采取一些措施来解除死锁,例如线程回滚资源剥夺等。
    超时机制:在请求资源时设置超时时间,如果等待时间超过超时时间,则放弃请求,避免无限期等待。

    1.4.3 活锁 (Livelocks)

    活锁(Livelock) 类似于死锁,也是指多个线程(或进程)因互相谦让而无限期地阻塞,导致程序无法继续执行的一种状态。与死锁不同的是,活锁中的线程并没有被阻塞,而是不断地重试某个操作,但由于某种原因,总是无法成功,导致程序一直循环执行,无法取得进展。

    经典案例:礼貌的行人

    想象一下,两条狭窄的走廊,两个行人在走廊中间相遇,都想给对方让路。

    1. 行人 A 向左边让路。
    2. 行人 B 也向左边让路。
    3. 两人又面对面相遇。
    4. 行人 A 向右边让路。
    5. 行人 B 也向右边让路。
    6. 两人又回到最初的位置,再次面对面相遇。

    如果两人一直重复上述让路动作,就会陷入活锁状态,虽然两人都在“努力”让路,但实际上谁也无法通过走廊,程序一直循环执行,无法取得进展。

    活锁与死锁的区别

    阻塞状态:死锁中的线程处于阻塞状态,等待资源;活锁中的线程处于非阻塞状态,不断地尝试执行操作。
    程序进展:死锁导致程序完全停止,无法继续执行;活锁导致程序持续运行,但无法取得任何进展。
    原因:死锁是由于资源竞争导致的;活锁是由于过度谦让不合理的重试机制导致的。

    避免活锁的常用方法

    随机退避:在重试操作之前,引入随机的等待时间,避免多个线程同时重试,导致活锁。
    优先级机制:为线程设置优先级,让优先级高的线程优先执行,避免低优先级线程一直重试,导致活锁。
    固定退避策略:使用固定的退避策略,例如每次重试等待的时间递增,直到达到最大等待时间,避免无限期重试。
    避免过度谦让:在设计并发程序时,避免过度谦让,例如不必要的重试、不合理的资源释放等。

    1.4.4 饥饿 (Starvation)

    饥饿(Starvation) 是指一个或多个线程(或进程)因为某种原因长期无法获得所需的资源,导致一直无法执行的一种状态。饥饿通常发生在资源分配不公平调度策略不合理的情况下。

    经典案例:不公平的 CPU 调度

    假设有一个多线程程序,其中包含一个高优先级线程和一个低优先级线程。如果 CPU 调度器总是优先调度高优先级线程,而忽略低优先级线程,那么低优先级线程就可能长期无法获得 CPU 时间片,导致饥饿。

    饥饿的常见原因

    优先级反转:高优先级线程等待低优先级线程释放资源,而低优先级线程又被其他中优先级线程抢占 CPU,导致高优先级线程长时间等待,发生优先级反转,也可能导致饥饿。
    不公平的资源分配:资源分配策略不公平,导致某些线程总是无法获得所需的资源。
    无限循环:某些线程进入无限循环,长时间占用资源,导致其他线程无法获得资源。

    避免饥饿的常用方法

    公平的资源分配策略:使用公平的资源分配策略,例如公平锁(Fair Lock)公平调度算法等,保证每个线程都有机会获得资源。
    优先级控制:合理设置线程优先级,避免优先级反转,可以使用优先级继承等机制来解决优先级反转问题。
    资源限制:对资源的使用进行限制,避免某些线程过度占用资源,导致其他线程饥饿。
    监控与报警:监控系统中线程的资源使用情况和执行状态,及时发现和解决饥饿问题。

    总结

    并发编程的挑战和陷阱主要包括竞态条件、死锁、活锁和饥饿。理解这些问题的原因和避免方法,是编写高质量并发程序的关键。在实际开发中,我们需要根据具体的应用场景和需求,选择合适的并发模型和同步机制,并进行充分的测试和验证,以确保程序的正确性、可靠性和性能。Folly Executor 框架正是为了帮助开发者更方便、更安全地进行并发编程而设计的,后续章节将深入探讨 Folly Executor 的各种特性和用法,帮助读者更好地应对并发编程的挑战。

    END_OF_CHAPTER

    2. chapter 2: Executor 模式:并发的基石 (Executor Pattern: The Cornerstone of Concurrency)

    2.1 Executor 模式的设计思想 (Design Philosophy of Executor Pattern)

    在深入探讨 folly::Executor 之前,我们首先需要理解 Executor 模式 (Executor Pattern) 的核心思想。Executor 模式,从本质上讲,是一种将任务的提交 (task submission)任务的执行 (task execution) 解耦的设计模式。这种解耦是并发编程中至关重要的一步,它为我们构建更灵活、更易于管理、更高效的并发程序奠定了坚实的基础。

    在传统的并发编程模型中,任务的提交和执行往往紧密耦合在一起。例如,直接使用 std::thread 创建线程来执行任务时,任务的定义和线程的创建、管理都由开发者显式地控制。 这种方式在简单的场景下尚可应对,但随着并发规模的扩大和业务逻辑的复杂化,其弊端也逐渐显现出来:

    资源管理复杂 (Complex Resource Management):开发者需要手动管理线程的生命周期,包括线程的创建、启动、销毁等。这不仅容易出错,而且在高并发场景下,频繁地创建和销毁线程会带来巨大的性能开销。

    缺乏灵活性 (Lack of Flexibility):任务的执行策略被硬编码在任务提交的代码中,例如,任务总是被提交到一个新的线程中执行。如果需要改变执行策略,例如使用线程池来复用线程,就需要修改大量的代码。

    代码可维护性差 (Poor Code Maintainability):任务提交和执行逻辑的耦合,使得代码结构混乱,可读性和可维护性降低。当需要调整并发策略或优化性能时,往往牵一发而动全身。

    Executor 模式正是为了解决上述问题而诞生的。它的核心思想在于引入一个 Executor (执行器) 抽象层,作为任务提交者 (Client) 和任务执行者 (Worker) 之间的中介。

    角色分离 (Separation of Roles)
    Executor 模式将并发编程中的角色明确地分离出来:
    ▮▮▮▮⚝ 任务提交者 (Task Submitter): 负责定义和提交待执行的任务,例如,业务逻辑代码、用户请求处理等。任务提交者只需要关注“做什么 (what to do)”,而无需关心“如何做 (how to do)” 以及 “何时做 (when to do)”。
    ▮▮▮▮⚝ Executor (执行器): 负责接收任务提交者提交的任务,并管理任务的执行。Executor 决定了任务的执行策略,例如,使用线程池、单线程、并行执行等。Executor 关注的是“如何做 (how to do)” 和 “何时做 (when to do)”。
    ▮▮▮▮⚝ 任务执行者 (Task Executor/Worker): 真正执行任务的角色,通常是线程或者进程。任务执行者从 Executor 获取任务并执行。

    抽象接口 (Abstract Interface)
    Executor 模式通过定义统一的 Executor 接口,来规范任务的提交和执行流程。任务提交者只需要面向 Executor 接口编程,而无需关心具体的 Executor 实现。这样就实现了任务提交和执行策略的解耦。folly::Executor 正是这样一个抽象接口,它定义了任务提交和执行的基本操作,例如 execute()schedule() 方法。

    策略模式的应用 (Application of Strategy Pattern)
    Executor 模式本质上是 策略模式 (Strategy Pattern) 在并发编程中的应用。不同的 Executor 实现 (例如 ThreadPoolExecutor, InlineExecutor, IOExecutor 等) 代表了不同的任务执行策略。通过选择不同的 Executor 实现,可以灵活地切换任务的执行策略,以适应不同的应用场景和性能需求。

    设计思想总结

    Executor 模式的核心设计思想可以概括为以下几点:

    解耦 (Decoupling): 将任务的提交和执行解耦,提高代码的灵活性和可维护性。
    抽象 (Abstraction): 通过 Executor 接口抽象任务执行策略,隐藏底层实现细节。
    策略 (Strategy): 不同的 Executor 实现代表不同的执行策略,可以根据需求灵活选择。
    资源管理 (Resource Management): Executor 负责管理任务执行所需的资源,例如线程池,降低资源管理的复杂性。

    通过理解 Executor 模式的设计思想,我们可以更好地理解 folly::Executor 的设计初衷和使用方法,并能更有效地利用它来构建高效、可靠的并发程序。

    2.2 Executor 模式的优势与应用场景 (Advantages and Application Scenarios of Executor Pattern)

    Executor 模式作为并发编程的基石,其广泛应用并非偶然。它所带来的诸多优势,使其在各种并发场景下都表现出色。理解 Executor 模式的优势和适用场景,能够帮助我们更好地选择和应用它,从而提升系统的性能、可维护性和可扩展性。

    Executor 模式的优势 (Advantages of Executor Pattern)

    提高代码组织性与可读性 (Improved Code Organization and Readability)
    Executor 模式将任务的提交和执行逻辑分离,使得代码结构更加清晰。任务提交者专注于业务逻辑,而 Executor 负责并发执行的细节。这种职责分离提高了代码的可读性和可维护性。

    提升资源利用率,降低开销 (Improved Resource Utilization and Reduced Overhead)
    通过使用线程池等 Executor 实现,可以有效地复用线程,避免频繁创建和销毁线程的开销。这不仅提高了系统的吞吐量,也降低了资源消耗,尤其在高并发、任务密集的场景下优势更加明显。

    增强灵活性和可配置性 (Enhanced Flexibility and Configurability)
    Executor 模式允许在不修改任务提交代码的情况下,灵活地切换任务的执行策略。例如,可以根据系统负载动态调整线程池的大小,或者在不同的环境中使用不同的 Executor 实现 (例如,测试环境使用 InlineExecutor,生产环境使用 ThreadPoolExecutor)。

    简化并发编程的复杂性 (Simplified Complexity of Concurrent Programming)
    Executor 模式抽象了底层的线程管理和调度细节,开发者无需直接操作线程,只需关注任务的定义和提交。这大大降低了并发编程的门槛,减少了出错的可能性。

    易于进行性能调优和监控 (Easy Performance Tuning and Monitoring)
    Executor 模式为性能调优和监控提供了便利。通过监控 Executor 的运行状态 (例如,线程池的活跃线程数、任务队列的长度等),可以了解系统的并发性能瓶颈,并根据监控数据进行相应的调优,例如调整线程池大小、优化任务队列等。

    提高代码的可测试性 (Improved Code Testability)
    由于 Executor 模式将任务执行策略解耦,因此可以更容易地对并发代码进行单元测试。例如,可以使用 InlineExecutor 在单线程环境下测试并发代码的逻辑正确性,而无需担心多线程环境下的复杂性。

    Executor 模式的应用场景 (Application Scenarios of Executor Pattern)

    Executor 模式几乎适用于所有需要并发处理的场景。以下是一些典型的应用场景:

    Web 服务器 (Web Servers)
    Web 服务器需要处理大量的并发请求。使用 Executor 模式,可以将每个请求的处理任务提交给 Executor 执行,例如使用 IOExecutor 处理 I/O 操作,使用 CPUThreadPoolExecutor 处理业务逻辑计算。这可以显著提高 Web 服务器的并发处理能力和响应速度。

    后台任务处理 (Background Task Processing)
    许多应用程序需要在后台执行一些耗时的任务,例如日志处理、数据同步、定时任务等。使用 Executor 模式,可以将这些后台任务提交给 Executor 异步执行,避免阻塞主线程,提高用户体验。

    并行计算 (Parallel Computing)
    对于计算密集型任务,可以使用 Executor 模式将任务分解成多个子任务,并行执行在多个线程上,充分利用多核处理器的计算能力,加速任务完成。例如,图像处理、科学计算、大数据分析等领域。

    事件驱动系统 (Event-Driven Systems)
    在事件驱动系统中,事件的产生和处理是异步的。Executor 模式可以用于处理事件,将每个事件的处理逻辑封装成任务,提交给 Executor 执行。例如,GUI 应用程序、网络编程、消息队列等。

    异步 I/O 操作 (Asynchronous I/O Operations)
    对于 I/O 密集型任务,例如网络请求、文件读写等,可以使用 IOExecutor 结合异步 I/O 技术 (例如 epoll, kqueue),实现高效的并发 I/O 处理。

    任务调度与定时任务 (Task Scheduling and Scheduled Tasks)
    Executor 模式可以结合定时器,实现任务的定时调度执行。例如,folly::ScheduledExecutor 可以在指定的时间或周期性地执行任务。

    总结

    Executor 模式的优势在于其解耦性、灵活性、高效性可维护性。它广泛应用于各种并发场景,能够有效地提高系统的并发处理能力、资源利用率和代码质量。在后续的章节中,我们将深入探讨 folly::Executor 的具体实现和应用,进一步了解如何利用 Executor 模式构建强大的并发程序。

    2.3 从 std::threadExecutor:演进之路 (From std::thread to Executor: The Evolution)

    为了更好地理解 Executor 模式的价值,我们需要回顾并发编程的演进历程,特别是从直接使用 std::thread 到引入 Executor 模式的转变。这个演进过程反映了并发编程从原始的手工管理到高级抽象的进步,也体现了软件工程领域不断追求简单性、效率和可维护性的趋势。

    std::thread 的原始并发模型 (Raw Concurrency Model with std::thread)

    在 C++11 引入 std::thread 之前,进行多线程编程通常需要依赖平台相关的 API (例如 POSIX Threads, Windows Threads)。 std::thread 的出现,为 C++ 带来了标准化的线程库,使得跨平台的多线程编程成为可能。

    使用 std::thread 创建线程并执行任务非常直接:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <thread>
    3
    4 void task_function() {
    5 std::cout << "Task executed by thread: " << std::this_thread::get_id() << std::endl;
    6 }
    7
    8 int main() {
    9 std::thread my_thread(task_function); // 创建线程并关联任务函数
    10 my_thread.join(); // 等待线程执行结束
    11 return 0;
    12 }

    这段代码简洁明了,展示了如何使用 std::thread 创建一个新线程来执行 task_function。 然而,当我们需要处理更复杂的并发场景时,直接使用 std::thread 的局限性就显现出来了:

    线程生命周期管理 (Thread Lifecycle Management)
    开发者需要显式地管理线程的创建和销毁。在高并发场景下,如果每个任务都创建一个新线程,会造成大量的线程创建和销毁开销,降低系统性能。同时,忘记 join()detach() 线程容易导致资源泄漏或程序行为不可预测。

    资源限制与控制 (Resource Limits and Control)
    直接使用 std::thread 难以有效地控制系统中线程的数量。无限制地创建线程可能会耗尽系统资源,导致系统崩溃。缺乏对线程池、任务队列等高级并发机制的支持。

    任务调度与策略 (Task Scheduling and Strategy)
    std::thread 本身只负责线程的创建和管理,不提供任务调度和执行策略的功能。如果需要实现复杂的任务调度 (例如优先级调度、延迟执行等) 或执行策略 (例如线程池、Fork-Join),需要开发者自行实现,增加了编程的复杂性。

    错误处理与异常传播 (Error Handling and Exception Propagation)
    在多线程环境下,异常处理和错误传播变得更加复杂。直接使用 std::thread 需要开发者手动处理线程中的异常,并将异常传播到主线程或其他线程,增加了错误处理的难度。

    Executor 模式的出现:更高层次的抽象 (Emergence of Executor Pattern: Higher Level of Abstraction)

    Executor 模式的出现,正是为了解决直接使用 std::thread 所带来的问题。它提供了一个更高层次的抽象,将任务的提交和执行解耦,并提供了更丰富的并发控制和管理功能。

    Executor 模式相对于 std::thread 的演进,主要体现在以下几个方面:

    线程池化 (Thread Pooling)
    Executor 模式的核心优势之一是线程池化。通过维护一个线程池,Executor 可以复用已创建的线程来执行新的任务,避免了频繁创建和销毁线程的开销,提高了系统性能和资源利用率。folly::ThreadPoolExecutor 就是一个强大的线程池实现。

    任务队列 (Task Queue)
    Executor 通常会维护一个任务队列,用于存储待执行的任务。任务提交者将任务提交到任务队列,Executor 从任务队列中取出任务并分配给线程执行。任务队列可以实现任务的缓冲和排队,提高系统的吞吐量和稳定性。

    执行策略 (Execution Strategy)
    Executor 模式允许定义和切换不同的任务执行策略。例如,可以使用线程池策略、单线程策略、Fork-Join 策略等。不同的策略适用于不同的应用场景和性能需求。folly::InlineExecutor, folly::IOExecutor, folly::CPUThreadPoolExecutor 等提供了多种执行策略的选择。

    生命周期管理 (Lifecycle Management)
    Executor 负责管理线程池的生命周期,包括线程池的创建、启动、停止和资源回收。开发者无需手动管理线程的生命周期,降低了资源管理的复杂性。folly::Executor 提供了 getKeepAliveToken() 等方法来管理 Executor 的生命周期。

    统一的接口 (Unified Interface)
    Executor 模式通过定义统一的 Executor 接口,规范了任务的提交和执行流程。任务提交者只需要面向 Executor 接口编程,而无需关心具体的 Executor 实现。这提高了代码的灵活性和可扩展性。

    演进总结

    std::thread 到 Executor 模式的演进,是并发编程从面向线程 (thread-oriented)面向任务 (task-oriented) 的转变。Executor 模式提供了一个更高层次的抽象,使得并发编程更加简单、高效和可维护。它将开发者从繁琐的线程管理细节中解放出来,让他们能够更专注于业务逻辑的实现。folly::Executor 正是这种演进趋势的体现,它提供了强大而灵活的 Executor 框架,帮助开发者构建现代化的并发应用程序。

    2.4 常见的 Executor 类型 (Common Executor Types)

    Executor 模式的强大之处在于其灵活性,通过不同的 Executor 实现,可以适应各种各样的并发场景和需求。 了解常见的 Executor 类型,能够帮助我们根据具体的应用场景选择合适的 Executor,从而最大化系统的性能和效率。

    2.4.1 线程池 (Thread Pool)

    线程池 (Thread Pool) 是最常用、也是最重要的 Executor 类型之一。它是一种线程复用 (thread reuse) 的技术,通过预先创建一组线程 (线程池),并将待执行的任务放入任务队列 (task queue) 中,线程池中的线程不断地从任务队列中取出任务并执行。当任务执行完毕后,线程不会立即销毁,而是返回线程池等待执行新的任务。

    线程池的工作原理 (Working Principle of Thread Pool)

    线程池初始化 (Thread Pool Initialization): 在程序启动时,线程池会根据配置参数 (例如,核心线程数、最大线程数等) 创建一定数量的线程,并将这些线程放入空闲线程队列 (idle thread queue) 中。

    任务提交 (Task Submission): 当任务提交者提交一个任务时,Executor 会将任务放入任务队列 (task queue) 中。任务队列通常是一个 FIFO 队列,用于存储待执行的任务。

    任务调度与执行 (Task Scheduling and Execution): 线程池中的线程不断地从任务队列中获取任务。
    ▮▮▮▮ⓑ 如果空闲线程队列不为空,则从队列中取出一个空闲线程,并将任务分配给该线程执行。
    ▮▮▮▮ⓒ 如果空闲线程队列为空,但任务队列未满,则任务会继续在任务队列中等待。
    ▮▮▮▮ⓓ 如果空闲线程队列为空,且任务队列已满,线程池会根据配置策略 (例如,饱和策略) 来处理新的任务,例如拒绝任务、抛出异常或阻塞提交线程。

    线程回收与复用 (Thread Recycling and Reuse): 当线程执行完任务后,不会立即销毁,而是将自身放回空闲线程队列,等待执行新的任务。如果线程在一段时间内没有任务可执行 (空闲时间超过一定阈值),线程池可能会回收部分线程,以减少资源消耗。

    线程池的优势 (Advantages of Thread Pool)

    降低线程创建和销毁开销 (Reduced Thread Creation and Destruction Overhead): 线程池通过复用线程,避免了频繁创建和销毁线程的开销,尤其在高并发、短生命周期任务的场景下,性能提升非常明显。

    提高系统响应速度 (Improved System Response Speed): 当有新任务到达时,线程池中可能已经有空闲线程可以直接执行任务,无需等待线程创建,从而提高了系统的响应速度。

    控制并发线程数量,防止资源耗尽 (Control Concurrent Thread Count and Prevent Resource Exhaustion): 线程池可以限制系统中并发执行的线程数量,防止无限制地创建线程导致系统资源耗尽。

    提供更丰富的并发控制和管理功能 (Provide Richer Concurrency Control and Management Features): 线程池通常提供更丰富的配置参数和管理功能,例如,线程池大小的动态调整、任务队列的选择、饱和策略的配置、线程的监控等。

    线程池的应用场景 (Application Scenarios of Thread Pool)

    线程池适用于各种需要并发处理的场景,尤其是在以下场景下表现出色:

    Web 服务器和应用服务器 (Web Servers and Application Servers): 处理大量的并发请求,提高服务器的吞吐量和响应速度。

    后台任务处理系统 (Background Task Processing Systems): 异步执行后台任务,例如日志处理、消息队列、定时任务等。

    批量数据处理 (Batch Data Processing): 并行处理大量数据,例如数据分析、ETL (Extract, Transform, Load) 等。

    微服务架构 (Microservices Architecture): 在微服务中,每个服务实例可以使用线程池来处理并发请求。

    folly::ThreadPoolExecutor 是 Folly 库提供的强大线程池实现,它提供了丰富的配置选项和高性能的任务调度能力,是构建高并发应用的首选 Executor 类型之一。在后续章节中,我们将深入探讨 folly::ThreadPoolExecutor 的具体使用和配置。

    2.4.2 单线程 Executor (Single-Thread Executor)

    单线程 Executor (Single-Thread Executor) 是一种只使用单个线程来执行任务的 Executor 类型。 尽管听起来与并发编程的目标有些背道而驰,但在某些特定场景下,单线程 Executor 却能发挥独特的作用。

    单线程 Executor 的工作原理 (Working Principle of Single-Thread Executor)

    单线程维护 (Single Thread Maintenance): 单线程 Executor 内部维护着一个工作线程 (worker thread) 和一个任务队列 (task queue)

    任务顺序执行 (Sequential Task Execution): 任务提交者提交的任务会被放入任务队列中。工作线程从任务队列中按照先进先出 (FIFO) 的顺序取出任务并串行执行

    任务队列缓冲 (Task Queue Buffering): 任务队列用于缓冲待执行的任务。即使任务提交速度超过了单线程的执行速度,任务也会被保存在队列中,等待被执行,保证了任务的顺序性和完整性。

    单线程 Executor 的优势 (Advantages of Single-Thread Executor)

    保证任务的顺序执行 (Guaranteed Sequential Task Execution): 单线程 Executor 确保所有提交的任务都按照提交的顺序依次执行,这在某些场景下非常重要,例如需要保证操作的原子性或顺序性的场景。

    简化并发控制 (Simplified Concurrency Control): 由于所有任务都在同一个线程中执行,因此无需考虑多线程并发带来的复杂性,例如竞态条件、死锁等问题。这大大简化了并发编程的难度。

    适用于特定类型的任务 (Suitable for Specific Types of Tasks): 单线程 Executor 适用于执行串行化任务 (serialized tasks)UI 线程任务 (UI thread tasks) 等场景。

    单线程 Executor 的应用场景 (Application Scenarios of Single-Thread Executor)

    UI 线程 (UI Thread): 在图形用户界面 (GUI) 编程中,UI 线程通常是单线程的。所有 UI 操作 (例如事件处理、界面更新) 都需要在 UI 线程中执行,以保证线程安全和界面响应性。单线程 Executor 可以用于管理 UI 线程中的任务。

    串行化任务处理 (Serialized Task Processing): 某些任务需要按照特定的顺序串行执行,例如日志记录、消息队列的消费者、某些状态机的状态转换等。单线程 Executor 可以保证这些任务的顺序执行。

    简化测试 (Simplified Testing): 在测试并发代码时,可以使用单线程 Executor 来模拟单线程环境,简化测试的复杂性,更容易发现并发代码中的逻辑错误。

    folly::InlineExecutor 可以被看作是一种特殊的单线程 Executor,它在提交任务的线程中同步执行任务,而不是创建一个新的线程。虽然 InlineExecutor 不是严格意义上的单线程 Executor (因为它没有独立的线程),但它也具有单线程 Executor 的某些特性,例如保证任务的顺序执行和简化并发控制。在后续章节中,我们将详细介绍 folly::InlineExecutor 的使用场景和特点。

    2.4.3 Fork-Join Executor (Fork-Join Executor)

    Fork-Join Executor (Fork-Join Executor) 是一种专门为分治算法 (divide and conquer algorithms)递归任务 (recursive tasks) 设计的 Executor 类型。 它利用工作窃取 (work-stealing) 算法,高效地执行可以分解成更小子任务的任务,充分利用多核处理器的并行计算能力。

    Fork-Join Executor 的工作原理 (Working Principle of Fork-Join Executor)

    任务分解 (Task Decomposition): Fork-Join Executor 适用于可以将任务分解成相互独立的子任务的场景。当一个任务被提交给 Fork-Join Executor 时,如果任务可以进一步分解,Executor 会将任务分解成多个子任务 (fork)。

    任务队列与工作线程 (Task Queues and Worker Threads): Fork-Join Executor 维护一个或多个工作线程 (worker threads),每个工作线程都有自己的双端队列 (deque) 作为任务队列。

    工作窃取 (Work-Stealing)
    ▮▮▮▮ⓑ 当一个工作线程完成自己队列中的任务后,如果还有其他任务需要执行,它不会空闲等待,而是尝试从其他工作线程的队列尾部 “窃取” 任务来执行 (work-stealing)。
    ▮▮▮▮ⓒ 工作窃取算法能够有效地平衡各个线程的任务负载,充分利用所有线程的计算能力,减少线程的空闲时间。

    任务合并 (Task Joining): 当子任务执行完成后,需要将子任务的结果合并 (join) 成最终结果。Fork-Join Executor 提供了机制来等待所有子任务完成,并获取子任务的执行结果。

    Fork-Join Executor 的优势 (Advantages of Fork-Join Executor)

    高效执行分治算法和递归任务 (Efficient Execution of Divide and Conquer Algorithms and Recursive Tasks): Fork-Join Executor 专门为分治算法和递归任务优化,能够高效地并行执行这些类型的任务。

    工作窃取算法提高并行度 (Work-Stealing Algorithm Improves Parallelism): 工作窃取算法能够动态地平衡任务负载,最大程度地利用多核处理器的并行计算能力,提高系统的吞吐量。

    减少线程竞争 (Reduced Thread Contention): 每个工作线程都有自己的任务队列,减少了线程之间的竞争,提高了并发性能。

    适用于 CPU 密集型任务 (Suitable for CPU-Intensive Tasks): Fork-Join Executor 特别适用于 CPU 密集型任务,例如计算密集型算法、数据处理、图像处理等。

    Fork-Join Executor 的应用场景 (Application Scenarios of Fork-Join Executor)

    并行排序算法 (Parallel Sorting Algorithms): 例如,并行归并排序、并行快速排序等,可以将排序任务分解成子任务并行执行,加速排序过程。

    并行搜索算法 (Parallel Search Algorithms): 例如,并行深度优先搜索、并行广度优先搜索等,可以并行搜索解空间,提高搜索效率。

    递归数据结构处理 (Recursive Data Structure Processing): 例如,并行树遍历、并行图算法等,可以并行处理递归数据结构。

    大数据分析 (Big Data Analytics): 在大数据分析领域,很多算法 (例如 MapReduce) 都采用了分治的思想,可以使用 Fork-Join Executor 来并行执行这些算法。

    folly 库本身并没有直接提供 ForkJoinExecutor 的实现,但 folly::CPUThreadPoolExecutor 在某些方面借鉴了 Fork-Join Executor 的思想,例如,它也适用于 CPU 密集型任务,并提供了一些机制来优化任务调度。在实际应用中,可以根据具体需求,基于 folly::Executor 接口自定义 Fork-Join Executor 的实现,或者使用其他库提供的 Fork-Join Executor 实现。

    总结

    不同的 Executor 类型适用于不同的并发场景。线程池 是通用的并发处理工具,适用于各种类型的任务;单线程 Executor 适用于需要保证任务顺序执行的场景;Fork-Join Executor 适用于分治算法和递归任务。选择合适的 Executor 类型,是构建高效并发程序的关键步骤。在后续章节中,我们将深入探讨 folly 提供的各种 Executor 实现,并结合实战案例,帮助读者掌握 folly::Executor 的应用技巧。

    END_OF_CHAPTER

    3. chapter 3: Folly Executor 概览 (Overview of Folly Executor)

    3.1 Folly 库简介 (Introduction to Folly Library)

    Folly,全称为 "Facebook Open-source Library",是由 Facebook 开源的一套 C++ 库。它旨在为 C++11 提供更好的基础设施组件,尤其侧重于构建高性能、高可靠性的应用程序。Folly 并非一个单一功能的库,而是一个包含众多模块的集合,涵盖了从基础数据结构、算法到网络编程、并发处理等多个领域。其设计哲学是实用至上,强调效率和工程实践,因此 Folly 库中的组件通常都经过了大规模、高负载环境的考验。

    Folly 库的诞生源于 Facebook 在构建其庞大社交网络平台时遇到的各种技术挑战。为了应对海量数据、高并发请求以及复杂的业务逻辑,Facebook 的工程师们不断探索和优化 C++ 的使用方式,并将这些经验和成果沉淀到 Folly 库中。因此,Folly 库不仅仅是一些代码的堆砌,更是 Facebook 多年工程实践经验的结晶。

    Folly 库包含众多模块,其中一些核心模块包括:

    Strings: 提供了 fbstring 等高效字符串处理类,针对性能进行了优化,尤其是在内存分配和拷贝方面。
    Collections: 包含了 fbvectorfbsetfbmap 等容器,以及各种高效的数据结构和算法,例如 F14ValueMapF14NodeMap 等,这些容器在特定场景下比标准库容器具有更好的性能。
    Concurrency: 提供了丰富的并发编程工具,包括 Executor 框架、Future/PromiseEventCountBaton 等,用于简化并发编程,提高程序性能和响应速度。Executor 框架正是本书的主题。
    IO: 提供了 SocketEventBaseAsyncSocket 等网络编程相关的组件,用于构建高性能的网络应用程序。
    JSON: 提供了快速的 JSON 解析和生成库 json
    Dynamic: 提供了 dynamic 类型,用于处理动态类型的数据,类似于 JavaScript 中的动态对象。

    Folly 库的设计目标是:

    高性能 (High Performance): Folly 库中的组件都经过了精心的性能优化,旨在提供极致的性能表现,满足高负载、低延迟的应用需求。
    可靠性 (Reliability): Folly 库在 Facebook 内部经过了长时间、大规模的实际应用验证,具有很高的可靠性和稳定性。
    易用性 (Usability): Folly 库提供了清晰、简洁的 API 设计,力求降低开发者的使用门槛,提高开发效率。
    可扩展性 (Extensibility): Folly 库的设计考虑了可扩展性,方便开发者根据自身需求进行定制和扩展。

    总而言之,Folly 库是一个强大而全面的 C++ 库,它汇集了 Facebook 在高性能计算领域的多年经验,为开发者提供了构建现代 C++ 应用所需的各种基础设施组件。对于追求卓越性能和可靠性的 C++ 开发者来说,Folly 库无疑是一个值得深入学习和使用的优秀工具库。

    3.2 为什么选择 Folly Executor (Why Choose Folly Executor)

    在并发编程领域,Executor 模式是一种被广泛认可和采用的设计模式,它将任务的提交和执行解耦,使得我们可以更加灵活地管理和调度并发任务。虽然 C++11 标准库也提供了 std::threadstd::async 等并发工具,以及 std::thread_pool (C++20 引入),但在构建复杂、高性能的并发系统时,原生的工具可能显得力不从心,或者使用起来较为繁琐。

    Folly Executor 框架正是在这样的背景下应运而生,它提供了一套更加强大、灵活且高效的 Executor 实现,可以更好地满足现代 C++ 应用对于并发处理的需求。选择 Folly Executor 的理由主要体现在以下几个方面:

    高性能和效率 (High Performance and Efficiency): Folly Executor 框架在设计之初就将性能放在首位。它提供了多种针对不同场景优化的 Executor 实现,例如 ThreadPoolExecutorIOExecutorCPUThreadPoolExecutor 等,这些实现都经过了精心的调优,能够充分利用系统资源,提供卓越的并发性能。相比于简单的 std::threadstd::async,Folly Executor 能够更有效地管理线程资源,减少线程创建和销毁的开销,提高任务的吞吐量和响应速度。

    丰富的 Executor 类型 (Rich Set of Executor Types): Folly Executor 框架提供了多种内置的 Executor 实现,每种实现都针对特定的应用场景进行了优化。例如:
    ▮▮▮▮⚝ ThreadPoolExecutor:通用的线程池实现,适用于 CPU 密集型和 I/O 密集型任务。
    ▮▮▮▮⚝ InlineExecutor:在提交任务的线程中同步执行任务,适用于测试、调试以及对延迟敏感的场景。
    ▮▮▮▮⚝ IOExecutor:专门为 I/O 密集型任务设计,通常与 EventBase 结合使用,能够高效处理大量的 I/O 事件。
    ▮▮▮▮⚝ CPUThreadPoolExecutor:针对 CPU 密集型任务进行了优化,可以更好地利用多核 CPU 的性能。
    ▮▮▮▮⚝ VirtualExecutor:允许在用户空间模拟线程执行,用于测试和特定场景下的优化。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 这些丰富的 `Executor` 类型使得开发者可以根据具体的应用场景选择最合适的 `Executor`,从而获得最佳的性能和资源利用率。

    灵活的任务调度和管理 (Flexible Task Scheduling and Management): Folly Executor 框架提供了灵活的任务调度和管理机制。例如,ThreadPoolExecutor 允许配置不同的任务队列类型(例如 FIFO 队列、优先级队列),以及自定义线程池的大小和线程管理策略。此外,Folly Executor 还支持任务的取消、中断以及优先级管理,可以更好地控制并发任务的执行。

    与 Folly 库的深度集成 (Deep Integration with Folly Library): Folly Executor 框架与 Folly 库的其他组件(例如 Future/PromiseEventBaseAsyncSocket 等)进行了深度集成,可以无缝地协同工作。例如,可以将 ExecutorFuture/Promise 结合使用,实现异步任务的编排和结果获取;可以将 IOExecutorEventBaseAsyncSocket 结合使用,构建高性能的网络应用程序。这种深度集成使得开发者可以更加方便地利用 Folly 库的强大功能,构建复杂的并发系统。

    良好的可扩展性和可定制性 (Good Extensibility and Customizability): Folly Executor 框架具有良好的可扩展性和可定制性。开发者可以根据自身需求,自定义 Executor 的实现,例如实现具有特定调度策略的 Executor,或者集成自定义的任务队列和线程管理机制。Folly Executor 框架的接口设计清晰、模块化,方便开发者进行扩展和定制。

    成熟度和社区支持 (Maturity and Community Support): Folly 库作为 Facebook 开源的核心组件之一,经过了多年的发展和迭代,在 Facebook 内部以及众多外部项目中得到了广泛应用,具有很高的成熟度和稳定性。同时,Folly 库拥有活跃的开源社区,提供了完善的文档和示例,开发者可以方便地获取帮助和支持。

    综上所述,选择 Folly Executor 框架,意味着选择了一种高性能、灵活、可扩展且成熟可靠的并发解决方案。无论是构建高并发的网络服务器,还是处理复杂的数据处理流水线,Folly Executor 都能为开发者提供强大的支持,帮助开发者更加高效、可靠地构建现代 C++ 并发应用程序。

    3.3 Folly Executor 的核心组件 (Core Components of Folly Executor)

    Folly Executor 框架的核心在于其组件化的设计,通过不同的组件协同工作,实现了强大的并发任务处理能力。理解 Folly Executor 的核心组件,有助于我们更好地理解其工作原理,并能够灵活地使用和扩展它。Folly Executor 的主要核心组件包括:

    folly::Executor 接口 ( folly::Executor Interface): folly::Executor 是整个 Executor 框架的基石,它定义了 Executor 的基本接口规范。任何符合 folly::Executor 接口的类都可以被视为一个 Executor,用于执行提交的任务。folly::Executor 接口主要包含以下几个核心方法:
    ▮▮▮▮⚝ execute(Func func): 提交一个函数对象 funcExecutor 中异步执行。这是最核心的方法,用于提交任务。
    ▮▮▮▮⚝ schedule(Func func, Duration delay): 提交一个函数对象 funcExecutor 中,延迟 delay 时间后执行。用于提交延迟任务。
    ▮▮▮▮⚝ getKeepAliveToken(): 获取一个 KeepAliveToken,用于管理 Executor 的生命周期。
    ▮▮▮▮⚝ 析构函数 ~Executor(): 用于 Executor 的资源清理和回收。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 `folly::Executor` 接口定义了一组简洁而强大的操作,使得不同的 `Executor` 实现可以统一地被使用和管理。

    Executor 实现 (Executor Implementations): Folly Executor 框架提供了多种 Executor 接口的具体实现,每种实现都针对不同的应用场景进行了优化。常见的 Executor 实现包括:
    ▮▮▮▮⚝ ThreadPoolExecutor: 基于线程池的 Executor 实现,是最常用的 Executor 类型,适用于各种并发任务。
    ▮▮▮▮⚝ InlineExecutor: 同步执行任务的 Executor 实现,主要用于测试和调试。
    ▮▮▮▮⚝ IOExecutor: 专为 I/O 密集型任务设计的 Executor 实现,通常与 EventBase 结合使用。
    ▮▮▮▮⚝ CPUThreadPoolExecutor: 针对 CPU 密集型任务优化的线程池 Executor 实现。
    ▮▮▮▮⚝ VirtualExecutor: 虚拟 Executor 实现,用于模拟线程执行,方便测试和资源控制。
    ▮▮▮▮⚝ ManualExecutor: 手动控制任务执行的 Executor 实现,用于特殊场景下的精细控制。
    ▮▮▮▮⚝ QueuedImmediateExecutor: 将任务放入队列后立即执行的 Executor 实现,用于保证任务的顺序执行。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 这些丰富的 `Executor` 实现为开发者提供了多样化的选择,可以根据具体的应用需求选择最合适的 `Executor` 类型。

    任务队列 (Task Queues): 在线程池类型的 Executor 实现中,任务队列是至关重要的组件。任务队列用于存储待执行的任务,线程池中的工作线程从任务队列中取出任务并执行。Folly Executor 框架支持多种任务队列类型,例如:
    ▮▮▮▮⚝ LifoSemMPMCQueue: 后进先出 (LIFO) 的多生产者多消费者 (MPMC) 队列。
    ▮▮▮▮⚝ MPSCQueue: 多生产者单消费者 (MPSC) 队列。
    ▮▮▮▮⚝ MPMCQueue: 多生产者多消费者 (MPMC) 队列。
    ▮▮▮▮⚝ PriorityQueue: 优先级队列,可以根据任务的优先级进行调度。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 不同的任务队列类型具有不同的性能特点和适用场景。例如,`LifoSemMPMCQueue` 在某些场景下可以提供更好的性能,而 `PriorityQueue` 可以用于实现任务的优先级调度。

    线程管理 (Thread Management): 对于线程池类型的 Executor,线程管理是核心功能之一。Folly Executor 框架提供了灵活的线程管理机制,例如:
    ▮▮▮▮⚝ 线程池大小的配置:可以设置线程池的初始大小、最大大小以及空闲线程的存活时间。
    ▮▮▮▮⚝ 线程的创建和销毁:线程池可以根据任务负载动态地创建和销毁线程,以优化资源利用率。
    ▮▮▮▮⚝ 线程的命名和属性设置:可以为线程池中的线程设置名称和属性,方便调试和监控。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 通过精细的线程管理,Folly Executor 可以有效地控制线程资源,避免线程过多或过少导致的问题,提高系统的稳定性和性能。

    KeepAliveToken (生命周期管理令牌): KeepAliveToken 是 Folly Executor 框架中用于管理 Executor 生命周期的一个重要机制。通过 getKeepAliveToken() 方法,可以获取一个 KeepAliveToken 对象。只要存在有效的 KeepAliveTokenExecutor 就不会被销毁。当所有 KeepAliveToken 对象都被销毁时,Executor 才会开始清理资源并最终销毁。这种机制可以确保在有任务正在执行或等待执行时,Executor 不会被意外销毁,从而保证程序的正确性。

    性能监控 (Performance Monitoring): Folly Executor 框架与 Folly 库的性能监控组件 PerfCounter 集成,可以方便地监控 Executor 的性能指标,例如任务提交数量、任务执行数量、队列长度、线程池大小等。通过性能监控,开发者可以及时了解 Executor 的运行状态,并进行性能调优。

    这些核心组件相互协作,共同构成了 Folly Executor 框架的强大功能。folly::Executor 接口定义了统一的规范,各种 Executor 实现提供了多样化的执行策略,任务队列负责任务的存储和调度,线程管理负责线程资源的控制,KeepAliveToken 管理 Executor 的生命周期,性能监控则提供了运行状态的可视化。理解这些组件及其相互关系,是深入掌握 Folly Executor 的关键。

    3.4 Folly Executor 的设计原则 (Design Principles of Folly Executor)

    Folly Executor 框架的设计并非随意而为,而是遵循了一系列明确的设计原则。这些原则指导了 Folly Executor 的架构设计、接口定义以及实现方式,使其具备高性能、高灵活性和良好的可扩展性。理解这些设计原则,有助于我们从更深层次理解 Folly Executor 的优势和特点。Folly Executor 的主要设计原则包括:

    接口与实现分离 (Interface and Implementation Separation): Folly Executor 框架的核心设计原则之一是接口与实现分离。folly::Executor 接口定义了 Executor 的抽象行为,而具体的 Executor 实现(例如 ThreadPoolExecutorInlineExecutor 等)则负责实现这些行为。这种设计模式带来了诸多好处:
    ▮▮▮▮⚝ 灵活性 (Flexibility): 开发者可以根据不同的应用场景选择不同的 Executor 实现,而无需修改上层代码。例如,在测试环境可以使用 InlineExecutor,而在生产环境可以使用 ThreadPoolExecutor
    ▮▮▮▮⚝ 可扩展性 (Extensibility): 开发者可以自定义 Executor 的实现,只要符合 folly::Executor 接口规范即可。这为框架的扩展提供了无限可能。
    ▮▮▮▮⚝ 可维护性 (Maintainability): 接口与实现分离降低了代码的耦合度,使得代码更加模块化,易于维护和修改。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 通过接口与实现分离,Folly Executor 框架实现了高度的灵活性和可扩展性,能够适应各种不同的并发场景和需求。

    关注性能 (Performance-Oriented): 性能是 Folly Executor 框架设计中最重要的考量因素之一。从 Executor 接口的设计到各种 Executor 实现的优化,都体现了对性能的极致追求。例如:
    ▮▮▮▮⚝ 高效的任务提交和调度: Executor 框架采用了高效的任务队列和调度算法,尽量减少任务提交和调度的开销。
    ▮▮▮▮⚝ 优化的线程池实现: ThreadPoolExecutor 等线程池实现经过了精心的调优,能够高效地管理线程资源,减少线程创建和销毁的开销。
    ▮▮▮▮⚝ 针对特定场景的优化: IOExecutorCPUThreadPoolExecutor 等专门针对 I/O 密集型和 CPU 密集型任务进行了优化,能够更好地利用硬件资源。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Folly Executor 框架的性能导向设计,使其在处理高并发、低延迟的应用场景时具有显著优势。

    易用性 (Usability): 虽然 Folly Executor 框架功能强大,但其设计也兼顾了易用性。folly::Executor 接口简洁明了,易于理解和使用。各种 Executor 实现也提供了合理的默认配置和易于配置的选项,使得开发者可以快速上手并灵活使用。例如:
    ▮▮▮▮⚝ 简洁的 API: folly::Executor 接口只包含几个核心方法,API 设计简洁清晰。
    ▮▮▮▮⚝ 合理的默认配置: ThreadPoolExecutorExecutor 实现提供了合理的默认配置,开发者在大多数情况下可以直接使用默认配置,无需进行复杂的参数调整。
    ▮▮▮▮⚝ 丰富的文档和示例: Folly 库提供了完善的文档和示例,帮助开发者快速学习和使用 Executor 框架。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Folly Executor 框架在保证强大功能的同时,也力求降低开发者的使用门槛,提高开发效率。

    可组合性 (Composability): Folly Executor 框架的设计考虑了组件之间的可组合性。Executor 可以与其他 Folly 库的组件(例如 Future/PromiseEventBase 等)以及其他库的组件进行灵活组合,构建更加复杂的并发系统。例如,可以将多个 Executor 组合成一个任务处理流水线,或者将 ExecutorFuture/Promise 结合使用,实现异步任务的编排和结果处理。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 可组合性使得 Folly Executor 框架可以更加灵活地应用于各种不同的并发场景,并与其他组件协同工作,发挥更大的作用。

    资源管理 (Resource Management): Folly Executor 框架非常注重资源管理,特别是线程资源的有效管理。例如,ThreadPoolExecutor 提供了线程池大小的动态调整、空闲线程的回收等机制,可以有效地控制线程资源的消耗,避免资源浪费或资源耗尽。KeepAliveToken 机制也用于管理 Executor 的生命周期,确保在不再需要 Executor 时及时释放资源。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 良好的资源管理是保证系统稳定性和性能的关键。Folly Executor 框架通过精细的资源管理机制,提高了系统的可靠性和资源利用率。

    错误处理 (Error Handling): Folly Executor 框架也考虑了错误处理机制。当任务执行过程中发生异常时,Executor 框架能够捕获异常并进行处理,防止异常扩散导致程序崩溃。例如,可以将 Future/PromiseExecutor 结合使用,捕获异步任务执行过程中抛出的异常,并进行相应的错误处理。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 完善的错误处理机制是保证程序健壮性的重要组成部分。Folly Executor 框架通过与 `Future`/`Promise` 等组件的协同工作,提供了强大的错误处理能力。

    可监控性 (Monitorability): Folly Executor 框架与 Folly 库的性能监控组件 PerfCounter 集成,提供了丰富的性能监控指标。开发者可以通过 PerfCounter 方便地监控 Executor 的运行状态,例如任务队列长度、线程池大小、任务执行耗时等。这些监控数据可以帮助开发者及时了解系统的性能瓶颈,并进行性能调优。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 良好的可监控性是系统运维和性能优化的重要基础。Folly Executor 框架通过提供丰富的性能监控指标,为开发者提供了强大的监控和调优工具。

    总而言之,Folly Executor 框架的设计原则体现了对高性能、灵活性、易用性、可扩展性、资源管理、错误处理和可监控性的全面考量。这些设计原则共同塑造了 Folly Executor 框架的优秀特性,使其成为构建现代 C++ 并发应用程序的理想选择。

    END_OF_CHAPTER

    4. chapter 4: folly::Executor 接口详解 (Detailed Explanation of folly::Executor Interface)

    4.1 folly::Executor 的基本接口定义 (Basic Interface Definition of folly::Executor)

    folly::Executor 是 Facebook Folly 库中用于抽象和管理执行单元的核心接口,它定义了一组操作,用于提交和控制任务的执行。可以将 folly::Executor 视为一个通用的任务执行器,它将任务的提交与任务的实际执行方式解耦。这种抽象使得我们可以方便地更换底层的执行机制,例如从线程池切换到单线程执行器,而无需修改任务提交的代码。

    folly::Executor 接口非常简洁,其核心定义如下(简化版本,省略了部分细节和宏定义):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class Executor {
    2 public:
    3 virtual ~Executor() = default;
    4
    5 virtual void execute(Func f) = 0;
    6
    7 virtual void schedule(Func f, std::chrono::milliseconds delay) = 0;
    8
    9 virtual std::unique_ptr<KeepAliveToken> getKeepAliveToken() = 0;
    10 };

    让我们逐一解析 folly::Executor 接口中的关键组成部分:

    virtual ~Executor() = default;: 虚析构函数。
    ▮▮▮▮⚝ folly::Executor 作为一个抽象基类,拥有虚析构函数是面向对象设计的基本原则。这确保了当通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,从而避免资源泄露等问题。
    ▮▮▮▮⚝ = default 表示使用默认的析构函数实现,通常情况下,默认析构函数已经足够满足需求。

    virtual void execute(Func f) = 0;: 纯虚函数 execute(),用于提交一个函数对象 f 以供执行。
    ▮▮▮▮⚝ Func 是一个占位符,实际上它可以是任何可调用对象,例如:
    ▮▮▮▮▮▮▮▮⚝ 函数指针(Function Pointer)
    ▮▮▮▮▮▮▮▮⚝ 函数对象(Functor)
    ▮▮▮▮▮▮▮▮⚝ Lambda 表达式(Lambda Expression)
    ▮▮▮▮⚝ execute()folly::Executor 最核心的方法,它定义了“执行”这一基本操作。具体的执行方式(例如,是在新线程中执行,还是在线程池中调度执行,还是同步执行)由 Executor 的具体实现类来决定。
    ▮▮▮▮⚝ = 0 表明 execute() 是一个纯虚函数,这意味着 folly::Executor 是一个抽象类,不能被直接实例化。任何想要使用 folly::Executor 的类都必须继承它,并提供 execute() 方法的具体实现。

    virtual void schedule(Func f, std::chrono::milliseconds delay) = 0;: 纯虚函数 schedule(),用于提交一个延迟执行的函数对象 f
    ▮▮▮▮⚝ schedule() 方法扩展了 execute() 的功能,允许任务在指定的延迟时间后执行。
    ▮▮▮▮⚝ std::chrono::milliseconds delay 参数指定了延迟的时间长度,单位为毫秒。
    ▮▮▮▮⚝ 与 execute() 类似,schedule() 的具体实现也由派生类负责,不同的 Executor 实现可能会有不同的延迟执行策略。并非所有的 Executor 都支持 schedule() 操作,某些简单的 Executor 实现可能会抛出异常或直接忽略延迟参数。

    virtual std::unique_ptr<KeepAliveToken> getKeepAliveToken() = 0;: 纯虚函数 getKeepAliveToken(),用于获取一个 KeepAliveToken 对象,用于管理 Executor 的生命周期。
    ▮▮▮▮⚝ KeepAliveToken 是 Folly 库中用于引用计数的一种机制,用于防止 Executor 在任务执行完成之前被意外销毁。
    ▮▮▮▮⚝ 通过持有 KeepAliveToken,可以延长 Executor 的生命周期,确保在 KeepAliveToken 对象被销毁之前,Executor 实例仍然有效。这在某些异步场景下非常重要,例如,当任务的生命周期可能超出 Executor 本身的生命周期时。
    ▮▮▮▮⚝ std::unique_ptr 表明 KeepAliveToken 的所有权由调用者独占,确保了资源管理的安全性。

    总而言之,folly::Executor 接口以其简洁的设计,抽象了并发编程中任务执行的核心概念。通过定义 execute()schedule()getKeepAliveToken() 等基本操作,folly::Executor 为构建灵活、可扩展的并发程序奠定了坚实的基础。开发者可以根据具体的应用场景选择或自定义不同的 Executor 实现,从而高效地管理和调度任务的执行。在后续章节中,我们将深入探讨 folly::Executor 的各种具体实现,例如 ThreadPoolExecutorInlineExecutorIOExecutorCPUThreadPoolExecutor 等,并结合实战代码,展示如何灵活运用 folly::Executor 构建高性能的并发应用。

    4.2 execute() 方法:任务提交的核心 (The execute() Method: Core of Task Submission)

    execute() 方法是 folly::Executor 接口中最核心、最基础的方法。它的职责非常明确:提交一个任务给 Executor 执行。正如我们在 4.1 节中看到的,execute() 方法的接口定义非常简洁:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual void execute(Func f) = 0;

    其中,Func f 代表要执行的任务,它可以是任何可调用对象。execute() 方法的设计哲学在于解耦:它将任务的提交者和任务的执行者解耦,提交者只需要关注提交任务,而无需关心任务具体是如何被执行的。具体的执行策略,例如任务是在新线程中执行、在线程池中调度执行、还是在当前线程同步执行,完全由 Executor 的具体实现类来决定。

    execute() 方法的语义

    execute() 方法通常以异步的方式提交任务。这意味着,当调用 executor->execute(task) 时,execute() 方法本身会立即返回,而不会等待 task 执行完成。任务 task 将会在稍后的某个时间点,由 Executor 内部的机制调度执行。

    任务的提交方式

    由于 execute() 方法接受任何可调用对象作为参数,因此我们可以非常灵活地提交各种类型的任务。以下是一些常见的任务提交方式:

    使用 Lambda 表达式提交任务

    Lambda 表达式是 C++11 引入的强大特性,它允许我们定义匿名的函数对象。使用 Lambda 表达式可以非常方便地定义和提交简单的任务:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 int main() {
    5 folly::InlineExecutor executor; // 使用 InlineExecutor,任务将在当前线程同步执行
    6
    7 executor.execute([] { // 提交一个 Lambda 表达式作为任务
    8 std::cout << "Task executed by InlineExecutor" << std::endl;
    9 });
    10
    11 std::cout << "Task submitted." << std::endl;
    12
    13 return 0;
    14 }

    在这个例子中,我们创建了一个 folly::InlineExecutor 实例。InlineExecutorexecute() 方法会在当前线程同步执行提交的任务。我们使用一个 Lambda 表达式 [] { ... } 定义了一个简单的任务,该任务的功能是打印一条消息。当我们调用 executor.execute(...) 提交任务后,Lambda 表达式会被立即执行,输出 "Task executed by InlineExecutor"。

    使用 Functor(函数对象)提交任务

    Functor 是指重载了 operator() 的类或结构体。我们可以创建一个 Functor 类,并将其实例作为任务提交给 Executor

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 class MyFunctor {
    5 public:
    6 void operator()() {
    7 std::cout << "Task executed by Functor" << std::endl;
    8 }
    9 };
    10
    11 int main() {
    12 folly::InlineExecutor executor;
    13 MyFunctor functor; // 创建 Functor 实例
    14
    15 executor.execute(functor); // 提交 Functor 实例作为任务
    16
    17 std::cout << "Task submitted." << std::endl;
    18
    19 return 0;
    20 }

    在这个例子中,我们定义了一个 MyFunctor 类,它重载了 operator()。我们创建了 MyFunctor 的一个实例 functor,并将其作为参数传递给 executor.execute() 方法。当 execute() 方法被调用时,functor.operator()() 将会被执行,输出 "Task executed by Functor"。

    使用 std::bind 绑定函数和参数

    std::bind 是 C++ 标准库提供的函数,它可以将函数和参数绑定在一起,生成一个新的可调用对象。我们可以使用 std::bind 绑定一个普通函数和一些参数,然后将生成的对象提交给 Executor

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3 #include <functional>
    4
    5 void myFunction(int arg) {
    6 std::cout << "Task executed by function, arg = " << arg << std::endl;
    7 }
    8
    9 int main() {
    10 folly::InlineExecutor executor;
    11
    12 auto boundFunction = std::bind(myFunction, 42); // 使用 std::bind 绑定 myFunction 和参数 42
    13
    14 executor.execute(boundFunction); // 提交绑定的函数对象
    15
    16 std::cout << "Task submitted." << std::endl;
    17
    18 return 0;
    19 }

    在这个例子中,我们定义了一个普通函数 myFunction,它接受一个 int 参数。我们使用 std::bind(myFunction, 42)myFunction 和参数 42 绑定在一起,生成一个新的函数对象 boundFunction。然后,我们将 boundFunction 提交给 executor.execute() 方法。当任务执行时,myFunction(42) 将会被调用,输出 "Task executed by function, arg = 42"。

    总结

    execute() 方法是 folly::Executor 接口的核心,它提供了任务提交的基本机制。通过接受各种可调用对象作为参数,execute() 方法提供了极大的灵活性,允许开发者以 Lambda 表达式、Functor、绑定函数等多种方式提交任务。理解 execute() 方法的异步语义和任务提交方式,是掌握 folly::Executor 的关键一步。在后续章节中,我们将看到 execute() 方法在各种 Executor 实现中的具体应用,以及如何利用 execute() 构建高效的并发程序。

    4.3 schedule() 方法:延迟任务执行 (The schedule() Method: Delayed Task Execution)

    schedule() 方法是 folly::Executor 接口提供的另一个重要方法,它扩展了 execute() 的功能,允许延迟执行提交的任务。schedule() 方法的接口定义如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual void schedule(Func f, std::chrono::milliseconds delay) = 0;

    execute() 方法类似,schedule() 方法也接受一个可调用对象 Func f 作为参数,表示要执行的任务。此外,schedule() 方法还接受一个 std::chrono::milliseconds delay 参数,用于指定任务的延迟执行时间,单位为毫秒。

    schedule() 方法的语义

    当调用 executor->schedule(task, delay) 时,schedule() 方法会将任务 task 提交给 Executor,并指示 Executor至少延迟 delay 毫秒之后再执行该任务。具体的延迟执行策略和精度取决于 Executor 的具体实现。

    延迟执行的应用场景

    延迟执行在并发编程中有很多应用场景,例如:

    定时任务: 周期性地执行某些任务,例如定时上报心跳、定时清理缓存等。虽然 folly::Executorschedule() 方法本身并不直接支持周期性任务,但我们可以通过在任务执行完成后再次调用 schedule() 方法来模拟周期性任务。更专业的定时任务调度可以使用 Folly 库中的 ScheduledExecutor 或其他专门的定时任务库。

    延迟重试: 在网络请求或某些操作失败后,延迟一段时间后进行重试。例如,当网络不稳定时,可以延迟几秒钟后重试发送请求。

    流量控制: 在高并发场景下,为了防止系统过载,可以使用延迟执行来控制任务的执行速率,实现流量削峰。

    GUI 编程: 在图形用户界面(GUI)编程中,某些操作需要在主线程(UI 线程)中延迟执行,以避免阻塞 UI 线程,保持界面的响应性。

    schedule() 方法的使用示例

    以下代码示例展示了如何使用 folly::InlineExecutorschedule() 方法来延迟执行任务:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3 #include <chrono>
    4 #include <thread>
    5
    6 int main() {
    7 folly::InlineExecutor executor;
    8
    9 std::cout << "Current time: " << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
    10
    11 executor.schedule([] {
    12 std::cout << "Task executed after delay. Time: " << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
    13 }, std::chrono::milliseconds(2000)); // 延迟 2000 毫秒 (2 秒) 执行任务
    14
    15 std::cout << "Schedule call finished immediately." << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
    16
    17 // 由于 InlineExecutor 是同步执行的,为了观察延迟效果,这里需要让主线程等待一段时间
    18 std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    19
    20 std::cout << "Main thread finished waiting." << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
    21
    22 return 0;
    23 }

    在这个例子中,我们首先获取并打印了当前时间戳。然后,我们调用 executor.schedule(...) 提交了一个延迟 2 秒执行的任务。任务的内容是打印一条消息和执行时间戳。由于我们使用的是 InlineExecutor,它的 schedule() 方法实际上会同步地等待延迟时间到达,然后再执行任务。因此,schedule() 调用会阻塞当前线程 2 秒钟。为了更清晰地展示延迟效果,我们在 schedule() 调用之后,又让主线程 sleep 了 3 秒钟。

    运行这段代码,你将会看到类似以下的输出(时间戳数值会因运行环境和时间而异):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Current time: 1678886400000000000
    2 Schedule call finished immediately.1678886400000000000
    3 Task executed after delay. Time: 1678886402000000000
    4 Main thread finished waiting.1678886403000000000

    从输出中可以看到,"Schedule call finished immediately." 几乎立即被打印出来,而 "Task executed after delay." 则在 2 秒延迟之后才被打印出来,验证了 schedule() 方法的延迟执行效果。

    注意事项

    并非所有 Executor 实现都支持 schedule() 方法。例如,某些非常简单的 Executor 实现可能只支持 execute() 方法,而不支持延迟执行。在这种情况下,调用 schedule() 方法可能会抛出异常,或者被视为调用 execute() 方法,忽略延迟参数。在使用 schedule() 方法之前,需要查阅具体 Executor 实现的文档,确认其是否支持延迟执行。

    延迟精度和保证schedule() 方法的 delay 参数指定的是最小延迟时间。实际的任务执行时间可能会略微晚于 delay 指定的时间,这取决于系统负载、调度器精度以及 Executor 的具体实现。folly::Executor 接口本身并不保证绝对的实时性或纳秒级的延迟精度。对于有严格实时性要求的应用场景,可能需要使用更专业的实时操作系统或实时调度器。

    总结

    schedule() 方法是 folly::Executor 接口对 execute() 方法的重要扩展,它提供了延迟任务执行的能力,使得 folly::Executor 能够应用于更广泛的并发场景,例如定时任务、延迟重试、流量控制等。理解 schedule() 方法的语义、应用场景和注意事项,能够帮助开发者更有效地利用 folly::Executor 构建功能丰富的并发应用。在后续章节中,我们将结合具体的 Executor 实现,进一步探讨 schedule() 方法的实际应用和高级用法。

    4.4 getKeepAliveToken() 方法:管理 Executor 生命周期 (The getKeepAliveToken() Method: Managing Executor Lifecycle)

    getKeepAliveToken() 方法是 folly::Executor 接口中用于管理 Executor 生命周期的关键方法。它的接口定义如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual std::unique_ptr<KeepAliveToken> getKeepAliveToken() = 0;

    getKeepAliveToken() 方法返回一个 std::unique_ptr<KeepAliveToken> 对象。KeepAliveToken 是 Folly 库中用于引用计数的一种机制,用于延长对象的生命周期,防止对象在被使用期间意外销毁。在 folly::Executor 的上下文中,KeepAliveToken 用于确保 Executor 实例在任务执行完成之前保持有效,即使原始的 Executor 对象可能已经超出作用域。

    KeepAliveToken 的作用原理

    KeepAliveToken 的工作原理基于RAII (Resource Acquisition Is Initialization)引用计数。当通过 executor->getKeepAliveToken() 获取一个 KeepAliveToken 对象时,Executor 内部的引用计数会增加。当 KeepAliveToken 对象被销毁(例如,超出作用域或被显式 delete)时,Executor 内部的引用计数会减少。只有当 Executor 的引用计数降为零时,Executor 实例才会被真正销毁。

    getKeepAliveToken() 的应用场景

    getKeepAliveToken() 方法主要用于以下场景:

    异步任务的生命周期管理: 在异步编程中,任务的生命周期可能超出创建 Executor 的作用域。例如,在一个函数中创建了一个 Executor,并提交了一些异步任务,然后函数返回。如果函数返回后 Executor 对象立即被销毁,那么正在执行的异步任务可能会因为 Executor 被销毁而出现问题(例如,访问已释放的资源)。使用 getKeepAliveToken() 可以确保在所有通过该 Executor 提交的任务完成之前,Executor 实例一直保持有效。

    Executor 的共享和传递: 当多个组件或模块需要共享同一个 Executor 实例时,可以使用 KeepAliveToken 来管理 Executor 的生命周期。每个组件或模块可以持有一个 KeepAliveToken,只要有一个 KeepAliveToken 存在,Executor 就不会被销毁。当所有组件或模块都释放了它们的 KeepAliveToken 后,Executor 才会最终被销毁。

    getKeepAliveToken() 的使用示例

    以下代码示例展示了如何使用 getKeepAliveToken() 来管理 ThreadPoolExecutor 的生命周期:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/executors/InlineExecutor.h>
    3 #include <iostream>
    4 #include <chrono>
    5 #include <thread>
    6
    7 using namespace folly;
    8
    9 int main() {
    10 // 创建一个 ThreadPoolExecutor,线程池大小为 2
    11 auto executor = std::make_shared<ThreadPoolExecutor>(2);
    12
    13 {
    14 // 获取一个 KeepAliveToken,延长 executor 的生命周期
    15 auto keepAliveToken = executor->getKeepAliveToken();
    16
    17 // 提交一个异步任务
    18 executor->execute([executor] {
    19 std::cout << "Task started. Executor use count: " << executor.use_count() << std::endl;
    20 std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时任务
    21 std::cout << "Task finished. Executor use count: " << executor.use_count() << std::endl;
    22 });
    23
    24 std::cout << "Task submitted. KeepAliveToken scope starts. Executor use count: " << executor.use_count() << std::endl;
    25
    26 // keepAliveToken 在这里超出作用域,但由于 executor 仍然被 std::shared_ptr 持有,且任务可能还在执行,executor 不会被立即销毁
    27 }
    28
    29 std::cout << "KeepAliveToken scope ends. Executor use count: " << executor.use_count() << std::endl;
    30
    31 // 等待一段时间,确保异步任务执行完成
    32 std::this_thread::sleep_for(std::chrono::seconds(3));
    33
    34 std::cout << "After task finished. Executor use count: " << executor.use_count() << std::endl;
    35
    36 // executor 在 main 函数结束时超出作用域,由于没有 KeepAliveToken 持有,且 std::shared_ptr 的引用计数降为 0,executor 将会被销毁
    37
    38 return 0;
    39 }

    在这个例子中,我们创建了一个 ThreadPoolExecutor,并使用 std::shared_ptr 来管理它的生命周期。在代码块 { ... } 内部,我们通过 executor->getKeepAliveToken() 获取了一个 KeepAliveToken 对象 keepAliveToken。这个 KeepAliveToken 对象会延长 executor 的生命周期。我们提交了一个异步任务给 executor,该任务会休眠 2 秒钟,模拟耗时操作。在代码块结束时,keepAliveToken 超出作用域被销毁,但由于 executor 仍然被 std::shared_ptr 持有,并且异步任务可能还在执行,executor 不会被立即销毁。只有当 main 函数结束,executorstd::shared_ptr 也超出作用域时,并且没有其他 KeepAliveToken 持有时,executor 才会被最终销毁。

    运行这段代码,你将会看到类似以下的输出:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 Task submitted. KeepAliveToken scope starts. Executor use count: 2
    2 KeepAliveToken scope ends. Executor use count: 2
    3 Task started. Executor use count: 2
    4 Task finished. Executor use count: 2
    5 After task finished. Executor use count: 1

    从输出中的 "Executor use count" 可以看到,在 KeepAliveToken 的作用域内,以及任务执行期间,executor 的引用计数都保持在 2 或以上,即使 keepAliveToken 超出作用域,executor 也不会被立即销毁。这确保了异步任务能够安全地执行完成。

    注意事项

    KeepAliveToken 通常与 std::shared_ptr 结合使用KeepAliveToken 主要用于管理 Executor 的生命周期,但它本身并不拥有 Executor 的所有权。通常情况下,我们会使用 std::shared_ptr 来管理 Executor 的所有权,并使用 KeepAliveToken 来延长其生命周期。

    避免循环引用。在使用 KeepAliveToken 时,需要注意避免循环引用,否则可能会导致内存泄露。例如,如果任务闭包中捕获了 KeepAliveToken 本身,就可能形成循环引用。

    KeepAliveToken 的生命周期管理KeepAliveToken 必须被正确地管理,确保在不再需要延长 Executor 生命周期时,KeepAliveToken 对象能够被及时销毁。通常情况下,KeepAliveToken 对象会使用 RAII 机制,在其作用域结束时自动销毁。

    总结

    getKeepAliveToken() 方法是 folly::Executor 接口中用于管理 Executor 生命周期的重要工具。通过 KeepAliveToken 机制,我们可以确保 Executor 实例在异步任务执行完成之前保持有效,避免因 Executor 提前销毁而导致的问题。理解 getKeepAliveToken() 的作用原理、应用场景和使用方法,能够帮助开发者更安全、更可靠地使用 folly::Executor 构建复杂的并发应用。在后续章节中,我们将结合具体的实战案例,进一步展示 getKeepAliveToken() 在异步编程中的应用。

    4.5 ~Executor() 析构函数:Executor 的清理 (The ~Executor() Destructor: Executor Cleanup)

    ~Executor() 析构函数是 folly::Executor 接口的最后一个组成部分,也是一个至关重要的部分。作为虚析构函数,~Executor() 负责清理 Executor 对象所占用的资源,确保在 Executor 对象生命周期结束时,能够正确地释放资源,避免内存泄露或其他资源管理问题。

    析构函数的作用

    析构函数是一个特殊的成员函数,当对象生命周期结束时,由系统自动调用。对于 folly::Executor 抽象类及其派生类来说,析构函数的主要职责包括:

    释放 Executor 内部管理的资源: 不同的 Executor 实现可能会管理不同的资源,例如:
    ▮▮▮▮⚝ ThreadPoolExecutor 需要管理线程池中的线程资源,包括线程的创建、销毁、回收等。
    ▮▮▮▮⚝ 某些 Executor 可能使用队列来存储待执行的任务,析构函数需要清理这些队列。
    ▮▮▮▮⚝ Executor 可能持有其他系统资源,例如文件句柄、网络连接等,析构函数需要释放这些资源。

    等待正在执行的任务完成: 对于某些 Executor 实现(例如 ThreadPoolExecutor),在析构时可能需要等待所有正在执行的任务完成,以确保任务的完整性和数据的一致性。当然,并非所有的 Executor 都需要等待任务完成,例如 InlineExecutor 由于是同步执行,析构时无需等待。具体的行为取决于 Executor 的实现策略。

    执行自定义的清理逻辑: 派生类可以在其析构函数中添加自定义的清理逻辑,例如日志记录、状态保存等。

    ~Executor() 的默认实现

    folly::Executor 接口的定义中,析构函数被声明为 = default

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual ~Executor() = default;

    = default 表示使用默认的析构函数实现。对于抽象基类 folly::Executor 来说,默认析构函数通常已经足够。具体的资源清理工作,会由 folly::Executor 的派生类在其析构函数中实现。

    派生类的析构函数实现

    folly::Executor 的各种派生类,例如 ThreadPoolExecutorInlineExecutorIOExecutorCPUThreadPoolExecutor 等,都提供了各自的析构函数实现,以完成特定于该 Executor 类型的资源清理工作。

    ThreadPoolExecutor 为例,其析构函数的主要工作包括:

    设置线程池状态为停止: 阻止新的任务提交到线程池。

    唤醒所有等待任务的线程: 通知线程池中的工作线程停止等待新任务。

    等待所有工作线程退出: 确保所有正在执行的任务完成,并且线程池中的线程都安全退出。

    释放线程池内部的资源: 例如,销毁线程池管理的线程对象、清理任务队列等。

    Executor 的清理过程

    当一个 folly::Executor 对象超出作用域,或者通过 delete 操作符显式销毁时,析构函数 ~Executor() 会被调用,启动 Executor 的清理过程。清理过程的具体步骤和行为取决于 Executor 的具体实现。

    注意事项

    确保 Executor 被正确销毁folly::Executor 对象必须被正确地销毁,才能确保资源被正确释放,避免资源泄露。通常情况下,使用 RAII 机制(例如 std::unique_ptrstd::shared_ptr)来管理 Executor 对象的生命周期,可以确保在 Executor 不再使用时,能够自动调用析构函数进行清理。

    析构函数的执行时间。某些 Executor 的析构函数(例如 ThreadPoolExecutor)可能需要等待正在执行的任务完成,因此析构函数的执行时间可能会比较长,特别是在任务执行时间较长或者任务数量较多的情况下。在某些对性能敏感的场景下,需要考虑析构函数的执行时间对程序性能的影响。

    避免在析构函数中抛出异常。C++ 规范建议析构函数不应该抛出异常。如果在析构函数中抛出异常,可能会导致程序崩溃或者资源泄露。因此,在自定义 Executor 派生类的析构函数时,应该尽量避免抛出异常,或者捕获并处理可能抛出的异常。

    总结

    ~Executor() 析构函数是 folly::Executor 接口中负责资源清理的关键组成部分。通过析构函数,folly::Executor 及其派生类能够确保在对象生命周期结束时,正确地释放资源,避免资源泄露,保证程序的健壮性和可靠性。理解析构函数的作用和清理过程,能够帮助开发者更好地管理 folly::Executor 对象的生命周期,构建高质量的并发程序。在后续章节中,我们将结合具体的 Executor 实现,进一步探讨析构函数在资源管理和程序稳定运行中的作用。

    END_OF_CHAPTER

    5. chapter 5: 常用 Folly Executor 实现 (Common Folly Executor Implementations)

    5.1 ThreadPoolExecutor:强大的线程池实现 ( ThreadPoolExecutor: Powerful Thread Pool Implementation)

    ThreadPoolExecutor 是 Folly 库中最常用、功能最强大的 Executor 实现之一,它提供了高度可配置和优化的线程池功能。线程池是一种经典的并发模式,通过维护一个线程集合来执行提交的任务,从而避免了频繁创建和销毁线程的开销,提高了并发执行效率和资源利用率。ThreadPoolExecutor 在处理各种类型的并发任务时都表现出色,尤其适用于 CPU 密集型和 I/O 密集型任务混合的场景。

    5.1.1 ThreadPoolExecutor 的构造与配置 (Construction and Configuration of ThreadPoolExecutor)

    ThreadPoolExecutor 提供了丰富的构造函数和配置选项,允许用户根据具体的应用场景和性能需求进行定制。以下是 ThreadPoolExecutor 常见的构造方式和配置参数:

    基本构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 auto tpExecutor = std::make_shared<folly::ThreadPoolExecutor>(
    2 threadCount, // 线程池大小
    3 std::make_shared<folly::LifoSemMPMCQueue<folly::Func>>()); // 任务队列

    这个构造函数接受两个主要参数:
    ▮▮▮▮ⓐ threadCount(线程池大小):指定线程池中常驻线程的数量。线程池会预先创建指定数量的线程,并保持这些线程处于活动状态,以便快速响应提交的任务。合理的线程池大小是性能调优的关键,需要根据系统的 CPU 核心数、任务类型和负载情况进行调整。
    ▮▮▮▮ⓑ taskQueue(任务队列):用于存储待执行的任务。ThreadPoolExecutor 允许用户自定义任务队列的类型,以满足不同的调度和性能需求。上述代码示例中使用的是 folly::LifoSemMPMCQueue<folly::Func>,这是一种基于信号量和多生产者多消费者(MPMC)队列实现的后进先出(LIFO)队列,具有较高的吞吐量和较低的延迟。

    配置线程工厂 (Thread Factory)
    可以通过 ThreadFactory 来自定义线程的创建方式,例如设置线程名称、优先级、栈大小等。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 auto threadFactory = std::make_shared<folly::NamedThreadFactory>("MyThreadPool");
    2 auto tpExecutor = std::make_shared<folly::ThreadPoolExecutor>(
    3 threadCount,
    4 std::make_shared<folly::LifoSemMPMCQueue<folly::Func>>(),
    5 threadFactory);

    folly::NamedThreadFactory 可以为线程池中的线程设置有意义的名称,方便在调试和监控时进行区分。

    配置拒绝策略 (Rejection Policy)
    当任务队列已满,且线程池中的线程都在忙碌时,新提交的任务将被拒绝执行。ThreadPoolExecutor 允许用户配置拒绝策略来处理被拒绝的任务。常见的拒绝策略包括:
    ▮▮▮▮ⓐ AbortPolicy(默认策略):直接抛出 std::runtime_error 异常,拒绝执行任务。
    ▮▮▮▮ⓑ DiscardPolicy:静默丢弃被拒绝的任务,不抛出任何异常。
    ▮▮▮▮ⓒ DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新提交当前任务。
    ▮▮▮▮ⓓ CallerRunsPolicy:由提交任务的线程来执行被拒绝的任务。
    可以通过 setRejectedExecutionHandler() 方法来设置拒绝策略:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 tpExecutor->setRejectedExecutionHandler(
    2 std::make_shared<folly::DiscardPolicy>());

    设置最大线程数 (Maximum Pool Size) 和 线程空闲超时 (Keep-Alive Time)
    ThreadPoolExecutor 还支持动态调整线程池的大小。可以设置最大线程数和线程空闲超时时间,当任务负载增加时,线程池可以动态创建新的线程来处理任务;当线程空闲时间超过设定的超时时间后,线程池可以回收空闲线程,从而节省系统资源。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 auto tpExecutor = std::make_shared<folly::ThreadPoolExecutor>(
    2 corePoolSize, // 核心线程数
    3 maxPoolSize, // 最大线程数
    4 keepAliveTime, // 线程空闲超时时间
    5 std::chrono::milliseconds(100), // 超时时间单位
    6 std::make_shared<folly::LifoSemMPMCQueue<folly::Func>>());

    ▮▮▮▮ⓐ corePoolSize(核心线程数):线程池中常驻线程的数量,即使线程空闲也不会被回收。
    ▮▮▮▮ⓑ maxPoolSize(最大线程数):线程池允许创建的最大线程数量。当任务队列已满,且当前线程数小于最大线程数时,线程池会创建新的线程来执行任务。
    ▮▮▮▮ⓒ keepAliveTime(线程空闲超时时间):当线程池中的线程空闲时间超过这个值时,且当前线程数大于核心线程数,空闲线程将被回收。

    通过灵活配置这些参数,ThreadPoolExecutor 可以适应各种不同的并发场景和性能需求。

    5.1.2 ThreadPoolExecutor 的任务队列与调度策略 (Task Queue and Scheduling Strategy of ThreadPoolExecutor)

    任务队列是 ThreadPoolExecutor 的核心组件之一,它负责存储待执行的任务,并根据一定的调度策略将任务分发给线程池中的线程执行。ThreadPoolExecutor 支持多种类型的任务队列,不同的任务队列具有不同的特性和适用场景。

    常见的任务队列类型
    ▮▮▮▮ⓑ folly::LifoSemMPMCQueue<folly::Func>:后进先出(LIFO)队列,基于信号量和 MPMC 队列实现。LIFO 队列的优点是可以提高缓存命中率,减少上下文切换,适用于对延迟敏感的任务。
    ▮▮▮▮ⓒ folly::FifoSemMPMCQueue<folly::Func>:先进先出(FIFO)队列,同样基于信号量和 MPMC 队列实现。FIFO 队列保证任务按照提交顺序执行,适用于需要保证任务执行顺序的场景。
    ▮▮▮▮ⓓ std::queue<folly::Func>:标准库的 FIFO 队列,线程不安全,需要外部加锁保护,性能相对较低。
    ▮▮▮▮ⓔ std::deque<folly::Func>:标准库的双端队列,线程不安全,同样需要外部加锁保护。
    ▮▮▮▮ⓕ 自定义队列:用户可以根据需要实现自定义的任务队列,例如优先级队列、延迟队列等。

    调度策略
    ThreadPoolExecutor 的默认调度策略是先提交先执行,即按照任务提交到任务队列的顺序进行调度。当线程池中有空闲线程时,会从任务队列头部取出一个任务进行执行。

    任务窃取 (Work Stealing)
    ThreadPoolExecutor 默认情况下不使用任务窃取策略。任务窃取是一种优化调度策略,在 Fork-Join Executor 中被广泛使用。任务窃取的思想是,当某个线程完成自己的任务后,可以从其他线程的任务队列中“窃取”任务来执行,从而提高线程池的整体利用率和负载均衡。如果需要使用任务窃取策略,可以考虑使用 folly::CPUThreadPoolExecutorfolly::ForkJoinExecutor

    优先级调度 (Priority Scheduling)
    ThreadPoolExecutor 本身不直接支持优先级调度。如果需要实现任务优先级调度,可以使用优先级队列作为任务队列,并自定义调度策略。例如,可以使用 std::priority_queuefolly::PriorityQueue 来实现优先级队列,并修改 ThreadPoolExecutor 的内部调度逻辑,使其能够根据任务的优先级进行调度。但这通常需要深入理解 ThreadPoolExecutor 的实现细节,并进行较为复杂的定制。更简单的做法是在提交任务时,通过 Functor 或 Lambda 表达式封装优先级信息,然后在任务执行前进行优先级判断和处理。

    选择合适的任务队列和调度策略对于 ThreadPoolExecutor 的性能至关重要。在实际应用中,需要根据任务的特性、性能需求和并发场景进行权衡和选择。对于大多数场景,folly::LifoSemMPMCQueuefolly::FifoSemMPMCQueue 都是不错的选择,它们具有较高的性能和较好的通用性。

    5.1.3 ThreadPoolExecutor 的线程管理与回收 (Thread Management and Recycling of ThreadPoolExecutor)

    ThreadPoolExecutor 负责线程的创建、管理和回收,有效地控制线程的生命周期,避免了手动管理线程的复杂性和潜在错误。

    线程的创建
    ThreadPoolExecutor 在以下情况下会创建新的线程:
    ▮▮▮▮ⓐ 线程池初始化时:根据 corePoolSize 参数,预先创建指定数量的线程。
    ▮▮▮▮ⓑ 任务提交时:当任务队列未满,且当前线程数小于 maxPoolSize 时,如果线程池中没有空闲线程,或者所有空闲线程都在执行其他任务,ThreadPoolExecutor 会创建新的线程来执行新提交的任务。

    线程的创建过程由 ThreadFactory 负责,用户可以通过自定义 ThreadFactory 来控制线程的创建细节,例如线程名称、栈大小、优先级等。

    线程的复用
    线程池的核心优势在于线程的复用。当线程执行完一个任务后,并不会立即销毁,而是会返回线程池中,等待执行新的任务。这种线程复用机制避免了频繁创建和销毁线程的开销,显著提高了并发性能。

    线程的回收
    ThreadPoolExecutor 会在以下情况下回收线程:
    ▮▮▮▮ⓐ 空闲超时回收:当线程池中的线程空闲时间超过 keepAliveTime 时,且当前线程数大于 corePoolSizeThreadPoolExecutor 会回收这些空闲线程,释放系统资源。
    ▮▮▮▮ⓑ 线程池销毁时:当 ThreadPoolExecutor 对象被销毁时,线程池会停止接受新的任务,并等待所有正在执行的任务完成后,回收所有线程。

    线程的回收过程是自动的,用户无需手动干预。ThreadPoolExecutor 通过内部的线程管理机制,保证线程池中的线程数量在合理的范围内波动,既能满足并发任务的需求,又能有效地控制资源消耗。

    线程池的生命周期管理
    ThreadPoolExecutor 的生命周期通常由其所在的对象管理。当 ThreadPoolExecutor 对象不再被引用时,其析构函数会被调用,线程池会被销毁。在销毁过程中,ThreadPoolExecutor 会执行以下操作:
    ▮▮▮▮ⓐ 停止接受新任务:设置内部状态,拒绝接受新的任务提交。
    ▮▮▮▮ⓑ 等待任务完成:等待任务队列中已有的任务和正在执行的任务完成。
    ▮▮▮▮ⓒ 回收所有线程:当所有任务完成后,回收线程池中的所有线程。

    为了确保线程池能够正常销毁,避免资源泄漏,建议使用 std::shared_ptr 来管理 ThreadPoolExecutor 对象,并确保在不再需要使用线程池时,及时释放 std::shared_ptr 的引用。

    通过精细的线程管理和回收机制,ThreadPoolExecutor 提供了高效、稳定、易用的线程池功能,是构建高性能并发应用的重要基石。

    5.2 InlineExecutor:同步执行的利器 ( InlineExecutor: Synchronous Execution Tool)

    InlineExecutor 是 Folly 库中一个非常轻量级的 Executor 实现,它并不会创建新的线程来执行任务,而是在当前线程同步执行提交的任务。InlineExecutor 的主要作用不是为了提高并发性能,而是为了提供一种方便的同步执行机制,以及在某些特定场景下简化代码逻辑和提高测试效率。

    5.2.1 InlineExecutor 的工作原理与适用场景 (Working Principle and Applicable Scenarios of InlineExecutor)

    InlineExecutor 的工作原理非常简单直接。当调用 InlineExecutor::execute(func) 方法提交任务时,InlineExecutor 会立即在调用 execute() 方法的线程中执行 func。这意味着任务的执行是同步的,execute() 方法会阻塞直到任务执行完成。

    适用场景

    单元测试 (Unit Testing)
    在单元测试中,我们通常希望代码的执行是可预测的和可控的。使用 InlineExecutor 可以确保任务在当前线程中同步执行,避免了多线程环境下的不确定性和复杂性,方便进行断点调试和结果验证。
    例如,在测试异步代码时,可以使用 InlineExecutor 来模拟异步执行环境,并同步地验证异步操作的结果。

    串行化执行 (Serialization)
    在某些场景下,我们需要确保任务按照提交顺序串行执行,但又不想引入额外的线程开销。InlineExecutor 可以实现任务的串行化执行,因为它总是在当前线程中同步执行任务,后提交的任务必须等待前一个任务执行完成后才能开始执行。

    简化同步代码 (Simplifying Synchronous Code)
    当代码逻辑主要以同步操作为主,但又需要使用 Executor 接口时,InlineExecutor 可以作为一个简单的替代方案。它可以避免引入额外的线程池,简化代码结构,降低复杂性。

    作为其他 Executor 的装饰器 (Decorator)
    InlineExecutor 可以作为其他 Executor 的装饰器,用于在某些特定情况下强制任务同步执行。例如,可以创建一个包装了 ThreadPoolExecutorInlineExecutor,在调试或性能分析时,可以切换到 InlineExecutor 来同步执行任务,方便观察和分析。

    不适用场景

    需要并发执行的场景
    InlineExecutor 本身不提供并发执行能力,它只是在当前线程中同步执行任务。如果需要提高并发性能,或者处理耗时任务,InlineExecutor 并不适用。

    I/O 密集型任务
    由于 InlineExecutor 在当前线程中同步执行任务,如果任务是 I/O 密集型的,会阻塞当前线程,影响程序的响应性和吞吐量。对于 I/O 密集型任务,应该使用 IOExecutorThreadPoolExecutor 等异步执行的 Executor

    总而言之,InlineExecutor 是一种轻量级、同步执行的 Executor 实现,适用于单元测试、串行化执行、简化同步代码以及作为其他 Executor 装饰器等特定场景。在需要并发执行或处理 I/O 密集型任务的场景下,应该选择其他合适的 Executor 实现。

    5.2.2 InlineExecutor 的代码示例与最佳实践 (Code Examples and Best Practices of InlineExecutor)

    代码示例

    基本使用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 int main() {
    5 folly::InlineExecutor executor;
    6
    7 executor.execute([] {
    8 std::cout << "Task 1 executed in thread: " << std::this_thread::get_id() << std::endl;
    9 });
    10
    11 executor.execute([] {
    12 std::cout << "Task 2 executed in thread: " << std::this_thread::get_id() << std::endl;
    13 });
    14
    15 std::cout << "Main thread: " << std::this_thread::get_id() << std::endl;
    16
    17 return 0;
    18 }

    这段代码演示了 InlineExecutor 的基本用法。任务 1 和任务 2 都会在 main 函数所在的线程中同步执行。输出结果会显示任务 1、任务 2 和主线程的线程 ID,它们应该是相同的。

    单元测试中的应用

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <gtest/gtest.h>
    3 #include <future>
    4
    5 int async_add(int a, int b, folly::Executor& executor) {
    6 std::promise<int> promise;
    7 std::future<int> future = promise.get_future();
    8
    9 executor.execute([&promise, a, b] {
    10 promise.set_value(a + b);
    11 });
    12
    13 return future.get();
    14 }
    15
    16 TEST(InlineExecutorTest, AsyncAddTest) {
    17 folly::InlineExecutor executor;
    18 int result = async_add(2, 3, executor);
    19 ASSERT_EQ(result, 5);
    20 }

    这个示例展示了 InlineExecutor 在单元测试中的应用。async_add 函数模拟了一个异步加法操作,使用 Executor 来执行加法任务。在测试用例 AsyncAddTest 中,我们使用 InlineExecutor 来执行 async_add 函数,并同步地获取结果,方便进行断言验证。

    最佳实践

    明确适用场景
    在使用 InlineExecutor 之前,需要明确其适用场景。InlineExecutor 适用于同步执行、单元测试、串行化执行等特定场景,不适用于需要并发执行或处理 I/O 密集型任务的场景。

    避免阻塞主线程
    虽然 InlineExecutor 的设计目的是同步执行,但在某些情况下,如果任务执行时间过长,仍然可能阻塞主线程,影响程序的响应性。因此,在使用 InlineExecutor 时,需要注意任务的执行时间,避免执行耗时过长的任务。

    与其他 Executor 配合使用
    InlineExecutor 可以与其他 Executor 配合使用,例如作为其他 Executor 的装饰器,或者在不同的代码模块中使用不同的 Executor。例如,在主线程中使用 InlineExecutor 进行同步操作,在后台线程中使用 ThreadPoolExecutorIOExecutor 进行异步操作。

    谨慎用于生产环境
    在生产环境中,通常需要考虑并发性能和资源利用率。InlineExecutor 由于是同步执行,不具备并发能力,因此在生产环境中应谨慎使用。只有在明确需要同步执行,且性能影响可接受的情况下,才能考虑在生产环境中使用 InlineExecutor

    总而言之,InlineExecutor 是一个简单而实用的工具,在特定场景下可以发挥重要作用。合理使用 InlineExecutor 可以简化代码逻辑、提高测试效率,但也需要注意其局限性,避免在不适用的场景下滥用。

    5.3 IOExecutor:专为 I/O 密集型任务设计 ( IOExecutor: Designed for I/O-Intensive Tasks)

    IOExecutor 是 Folly 库中专门为 I/O 密集型任务 设计的 Executor 实现。与 ThreadPoolExecutor 相比,IOExecutor 在处理 I/O 密集型任务时具有更高的效率和更好的性能。IOExecutor 的核心思想是利用非阻塞 I/O事件循环 机制,充分利用系统资源,提高 I/O 并发处理能力。

    5.3.1 IOExecutor 的特性与优势 (Features and Advantages of IOExecutor)

    IOExecutor 具有以下主要特性和优势:

    非阻塞 I/O (Non-blocking I/O)
    IOExecutor 基于非阻塞 I/O 模型。当任务提交到 IOExecutor 后,如果任务涉及到 I/O 操作,IOExecutor 会将 I/O 操作设置为非阻塞模式,并立即返回。当 I/O 操作完成时,系统会通过事件通知机制(例如 epoll, kqueue)通知 IOExecutorIOExecutor 再继续执行任务的后续部分。
    非阻塞 I/O 的优势在于,线程在等待 I/O 操作完成时不会被阻塞,可以继续处理其他任务,从而提高了线程的利用率和系统的并发处理能力。

    事件循环 (Event Loop)
    IOExecutor 内部维护一个事件循环。事件循环负责监听 I/O 事件(例如 socket 可读、可写事件),并将就绪的事件分发给相应的任务处理。事件循环是实现非阻塞 I/O 的关键机制,它使得单线程也能高效地处理大量的并发 I/O 操作。

    单线程或少量线程 (Single-threaded or Few Threads)
    IOExecutor 通常使用单线程少量线程来处理 I/O 任务。由于非阻塞 I/O 和事件循环机制已经能够高效地处理并发 I/O 操作,因此不需要像 ThreadPoolExecutor 那样创建大量的线程。使用少量线程可以减少线程上下文切换的开销,提高系统性能。在 Folly 库中,IOExecutor 默认使用单线程。

    高并发 I/O 处理能力 (High-Concurrency I/O Processing)
    基于非阻塞 I/O 和事件循环,IOExecutor 能够高效地处理大量的并发 I/O 连接和请求。它特别适用于网络编程、文件 I/O 等 I/O 密集型场景,例如构建高性能的网络服务器、代理服务器、文件服务器等。

    资源效率 (Resource Efficiency)
    IOExecutor 使用少量线程甚至单线程来处理 I/O 任务,相比于为每个 I/O 连接创建一个线程的传统多线程模型,IOExecutor 能够显著节省系统资源,例如线程创建和销毁的开销、线程上下文切换的开销、内存占用等。

    与 Folly 异步编程框架集成 (Integration with Folly Asynchronous Programming Framework)
    IOExecutor 与 Folly 库的异步编程框架(例如 Future/Promise协程 (Coroutine))紧密集成,可以方便地构建基于 IOExecutor 的异步 I/O 应用。例如,可以使用 folly::AsyncSocket 进行非阻塞 socket I/O 操作,并使用 IOExecutor 来调度 I/O 事件回调。

    适用场景

    网络编程 (Network Programming)
    构建高性能网络服务器、客户端、代理服务器、负载均衡器等。IOExecutor 可以高效地处理大量的并发网络连接和请求。

    文件 I/O (File I/O)
    处理大量的文件读写操作,例如文件服务器、日志处理、数据备份等。

    数据库客户端 (Database Client)
    构建高性能数据库客户端,处理大量的数据库查询和事务操作。

    消息队列 (Message Queue)
    构建高性能消息队列系统,处理大量的消息收发操作。

    总而言之,IOExecutor 是专为 I/O 密集型任务设计的 Executor 实现,它利用非阻塞 I/O 和事件循环机制,具有高并发 I/O 处理能力、资源效率和良好的可扩展性,是构建高性能 I/O 密集型应用的首选 Executor

    5.3.2 IOExecutor 在网络编程中的应用 (Application of IOExecutor in Network Programming)

    IOExecutor 在网络编程中有着广泛的应用,特别是在构建高性能网络服务器方面,IOExecutor 发挥着至关重要的作用。

    构建高性能网络服务器
    使用 IOExecutor 可以构建基于事件驱动非阻塞 I/O 的高性能网络服务器。传统的多线程服务器模型为每个客户端连接创建一个线程,当连接数增加时,线程创建和上下文切换的开销会变得非常巨大,限制了服务器的并发处理能力。而基于 IOExecutor 的服务器可以使用单线程少量线程来处理大量的并发连接,显著提高了服务器的吞吐量和响应速度。

    工作流程
    ⚝ 服务器启动时,创建一个 IOExecutor 实例。
    ⚝ 服务器监听端口,接受客户端连接。
    ⚝ 当有新的客户端连接建立时,将连接的 socket 注册到 IOExecutor 的事件循环中,监听 socket 的可读事件
    ⚝ 当 socket 可读事件发生时,IOExecutor 会调度相应的读回调函数来处理客户端请求。
    ⚝ 在读回调函数中,服务器读取客户端发送的数据,进行业务逻辑处理,并将响应数据写回客户端 socket。
    ⚝ 将 socket 注册到 IOExecutor 的事件循环中,监听 socket 的可写事件
    ⚝ 当 socket 可写事件发生时,IOExecutor 会调度相应的写回调函数来发送响应数据。
    ⚝ 如此循环,服务器在一个或少量线程中高效地处理大量的并发连接。

    处理网络 I/O 操作
    在网络编程中,常见的 I/O 操作包括 socket 的accept(接受连接)、read(读取数据)、write(发送数据)、connect(连接服务器)等。这些 I/O 操作都可以通过 IOExecutor 来异步执行。Folly 库提供了 folly::AsyncSocket 类,它是基于 IOExecutor 的非阻塞 socket 封装,可以方便地进行异步 socket I/O 操作。

    示例代码 (使用 folly::AsyncSocketIOExecutor)

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/io/async/AsyncSocket.h>
    2 #include <folly/executors/IOExecutor.h>
    3 #include <iostream>
    4
    5 void onDataReceived(folly::AsyncSocket* sock, std::unique_ptr<folly::IOBuf> data) {
    6 std::cout << "Received data: " << data->moveToFbString() << std::endl;
    7 // 处理接收到的数据
    8 sock->close(); // 关闭连接
    9 }
    10
    11 void onConnectSuccess(folly::AsyncSocket* sock) {
    12 std::cout << "Connected to server successfully!" << std::endl;
    13 sock->write("Hello, server!"); // 发送数据
    14 sock->setReadCB(new folly::AsyncSocket::ReadCallbackT<decltype(onDataReceived)>(onDataReceived)); // 设置读回调
    15 }
    16
    17 void onConnectError(folly::AsyncSocket* sock, const folly::AsyncSocketException& ex) {
    18 std::cerr << "Failed to connect to server: " << ex.what() << std::endl;
    19 sock->close(); // 关闭连接
    20 }
    21
    22 int main() {
    23 folly::IOExecutor executor;
    24 folly::AsyncSocket::UniquePtr sock(new folly::AsyncSocket(&executor));
    25
    26 folly::SocketAddress addr("127.0.0.1", 8888); // 服务器地址
    27
    28 sock->connect(addr, 3000, // 连接超时时间 3 秒
    29 new folly::AsyncSocket::ConnectCallbackT<decltype(onConnectSuccess), decltype(onConnectError)>(onConnectSuccess, onConnectError)); // 设置连接回调
    30
    31 executor.loopForever(); // 启动事件循环
    32
    33 return 0;
    34 }

    这段代码演示了如何使用 folly::AsyncSocketIOExecutor 创建一个简单的 TCP 客户端。客户端连接到服务器,发送 "Hello, server!" 消息,并接收服务器的响应数据。所有的网络 I/O 操作都是异步的,由 IOExecutor 的事件循环驱动。

    实现网络协议
    IOExecutor 可以用于实现各种网络协议,例如 HTTP、TCP、UDP、WebSocket 等。基于 IOExecutor 的异步 I/O 模型可以方便地处理协议的复杂状态和流程,提高协议处理的效率和并发能力。

    构建网络中间件
    例如负载均衡器、反向代理、API 网关等网络中间件,通常需要处理大量的并发网络连接和请求转发。IOExecutor 可以作为这些中间件的核心组件,提供高性能的网络 I/O 处理能力。

    总而言之,IOExecutor 在网络编程中扮演着重要的角色,它为构建高性能、高并发、可扩展的网络应用提供了强大的基础支持。掌握 IOExecutor 的使用,对于开发高效的网络程序至关重要。

    5.4 CPUThreadPoolExecutor:针对 CPU 密集型任务优化 ( CPUThreadPoolExecutor: Optimized for CPU-Intensive Tasks)

    CPUThreadPoolExecutor 是 Folly 库中专门为 CPU 密集型任务 优化的 Executor 实现。与通用的 ThreadPoolExecutor 相比,CPUThreadPoolExecutor 在处理 CPU 密集型任务时,更加注重减少线程上下文切换提高 CPU 缓存命中率,从而获得更好的性能。

    5.4.1 CPUThreadPoolExecutor 的设计考量 (Design Considerations of CPUThreadPoolExecutor)

    CPUThreadPoolExecutor 的设计主要考虑以下几个方面:

    线程数量与 CPU 核心数匹配 (Thread Count Matching CPU Cores)
    对于 CPU 密集型任务,最佳的线程数量通常与 CPU 的核心数相匹配。如果线程数量过多,反而会增加线程上下文切换的开销,降低性能。CPUThreadPoolExecutor 的默认线程数量通常设置为 CPU 的核心数,或者略少于核心数,以充分利用 CPU 资源,同时避免过多的上下文切换。

    任务队列的选择 (Task Queue Selection)
    CPUThreadPoolExecutor 通常选择无锁队列轻量级锁队列作为任务队列,以减少队列操作的开销。例如,folly::LifoSemMPMCQueuefolly::FifoSemMPMCQueue 都是不错的选择。此外,CPUThreadPoolExecutor 可能会针对 CPU 缓存优化队列的内存布局,提高缓存命中率。

    任务窃取策略 (Work Stealing Strategy)
    CPUThreadPoolExecutor 通常会采用任务窃取策略。任务窃取的思想是,当某个线程完成自己的任务后,可以从其他线程的任务队列中“窃取”任务来执行,从而提高线程池的整体利用率和负载均衡。任务窃取策略可以有效地减少线程空闲时间,提高 CPU 的利用率。

    线程亲和性 (Thread Affinity)
    在某些情况下,CPUThreadPoolExecutor 可能会考虑线程亲和性。线程亲和性是指将线程绑定到特定的 CPU 核心上执行。通过线程亲和性,可以提高 CPU 缓存的命中率,减少跨 CPU 核心的数据迁移,从而提高 CPU 密集型任务的性能。但线程亲和性也可能带来一些复杂性,例如负载不均衡等问题,需要根据具体场景进行权衡。

    NUMA (Non-Uniform Memory Access) 感知
    对于 NUMA 架构的系统,CPUThreadPoolExecutor 可能会考虑 NUMA 感知。NUMA 架构下,不同 CPU 核心访问本地内存的速度比访问远程内存的速度快得多。NUMA 感知的 CPUThreadPoolExecutor 会尽量将线程和其需要访问的数据分配到同一个 NUMA 节点上,以提高内存访问速度,减少跨 NUMA 节点的内存访问延迟。

    避免 I/O 操作 (Avoiding I/O Operations)
    CPUThreadPoolExecutor 主要用于处理 CPU 密集型任务,应该尽量避免在 CPUThreadPoolExecutor 中执行 I/O 操作。如果任务中包含 I/O 操作,应该将 I/O 操作剥离出来,使用 IOExecutor 或其他合适的 Executor 来处理 I/O 任务,将 CPU 密集型计算部分交给 CPUThreadPoolExecutor 处理。

    总而言之,CPUThreadPoolExecutor 的设计目标是最大化 CPU 的利用率,减少线程上下文切换和内存访问延迟,从而在处理 CPU 密集型任务时获得最佳性能。

    5.4.2 CPUThreadPoolExecutorThreadPoolExecutor 的对比 (Comparison between CPUThreadPoolExecutor and ThreadPoolExecutor)

    CPUThreadPoolExecutorThreadPoolExecutor 都是 Folly 库提供的线程池实现,但它们在设计目标和适用场景上有所不同。以下是 CPUThreadPoolExecutorThreadPoolExecutor 的主要对比:

    特性/方面CPUThreadPoolExecutorThreadPoolExecutor
    设计目标针对 CPU 密集型任务优化通用线程池,适用于各种类型的任务
    线程数量通常与 CPU 核心数匹配,默认值可能为 std::thread::hardware_concurrency()可配置,根据具体场景调整
    任务队列倾向于选择无锁队列或轻量级锁队列,可能针对 CPU 缓存优化可配置,支持多种队列类型,例如 FIFO、LIFO 等
    调度策略通常采用任务窃取策略默认先提交先执行,可配置拒绝策略
    线程亲和性可能考虑线程亲和性,提高 CPU 缓存命中率一般不考虑线程亲和性
    NUMA 感知可能考虑 NUMA 感知,优化 NUMA 架构下的性能一般不考虑 NUMA 感知
    适用任务类型CPU 密集型任务,例如计算密集型算法、数据分析、图像处理等CPU 密集型、I/O 密集型、混合型任务
    性能特点减少线程上下文切换,提高 CPU 缓存命中率,最大化 CPU 利用率通用性强,可配置性高,适用于各种并发场景
    资源消耗线程数量相对较少,资源消耗相对较低线程数量可配置,资源消耗可根据线程池大小调整
    使用场景示例大规模数值计算、科学计算、视频编码、机器学习模型训练等网络服务器、消息队列、通用后台任务处理、各种并发应用

    总结

    CPUThreadPoolExecutor 专注于CPU 密集型任务的性能优化,通过匹配 CPU 核心数、任务窃取、线程亲和性等策略,最大化 CPU 的利用率,减少不必要的开销。它适用于需要大量 CPU 计算的任务,例如科学计算、数据分析、视频编码等。

    ThreadPoolExecutor 是一个通用线程池,适用于各种类型的任务,包括 CPU 密集型、I/O 密集型和混合型任务。它提供了丰富的配置选项,可以根据具体的应用场景和性能需求进行定制。ThreadPoolExecutor 的通用性更强,适用范围更广,是构建各种并发应用的基础组件。

    在实际应用中,应该根据任务的类型和特性选择合适的 Executor。如果任务主要是 CPU 密集型的,且对性能要求较高,可以优先考虑使用 CPUThreadPoolExecutor。如果任务类型比较复杂,或者不确定任务类型,可以使用通用的 ThreadPoolExecutor,并根据实际情况进行调优。对于 I/O 密集型任务,则应该选择 IOExecutor

    END_OF_CHAPTER

    6. chapter 6: 任务提交与管理 (Task Submission and Management)

    6.1 使用 Lambda 表达式提交任务 (Submitting Tasks using Lambda Expressions)

    在并发编程中,任务提交是 Executor 模式的核心操作之一。folly::Executor 接口的设计旨在解耦任务的提交与执行,使得开发者可以专注于任务逻辑本身,而无需关心任务是如何被调度和执行的。现代 C++ 提供了强大的 Lambda 表达式(Lambda Expression),它为我们提供了一种简洁、灵活的方式来定义匿名函数对象(anonymous function object),非常适合用于向 folly::Executor 提交任务。

    Lambda 表达式简介

    Lambda 表达式是 C++11 引入的一项重要特性,它允许我们在代码中定义匿名函数。Lambda 表达式的基本语法形式如下:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 [capture list](parameter list) -> return type {
    2 // 函数体
    3 }

    捕获列表(capture list):指定了 Lambda 表达式可以访问的外部变量,以及访问方式(值捕获或引用捕获)。
    参数列表(parameter list):与普通函数的参数列表类似,指定了 Lambda 表达式接受的参数。可以省略。
    返回类型(return type):指定了 Lambda 表达式的返回值类型。在简单情况下可以省略,编译器会自动推导。
    函数体(function body):包含了 Lambda 表达式的具体执行代码。

    Lambda 表达式的引入极大地简化了函数对象的创建,尤其是在需要传递简短、局部性强的函数逻辑时,Lambda 表达式比传统的函数对象或函数指针更加方便和易用。

    使用 Lambda 表达式提交任务

    folly::Executorexecute() 方法接受一个 folly::Func 类型的参数,而 Lambda 表达式可以很方便地转换为 folly::Func,因此我们可以直接使用 Lambda 表达式来定义并提交任务。

    以下代码示例展示了如何使用 Lambda 表达式向 folly::ThreadPoolExecutor 提交任务:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/ScopeGuard.h>
    3 #include <iostream>
    4
    5 int main() {
    6 // 创建一个线程池 Executor,包含 4 个线程
    7 folly::ThreadPoolExecutor executor(4);
    8 // 确保 executor 在 main 函数结束时被销毁,防止资源泄露
    9 FOLLY_SCOPE_GUARD {
    10 executor.shutdown();
    11 executor.join();
    12 };
    13
    14 // 使用 Lambda 表达式提交一个简单的任务
    15 executor.execute([&]() {
    16 std::cout << "Task executed by thread: " << std::this_thread::get_id() << std::endl;
    17 });
    18
    19 // 提交带有参数的 Lambda 表达式任务
    20 int task_id = 1;
    21 executor.execute([task_id]() {
    22 std::cout << "Task " << task_id << " executed by thread: " << std::this_thread::get_id() << std::endl;
    23 });
    24
    25 // 提交捕获外部变量的 Lambda 表达式任务
    26 int counter = 0;
    27 executor.execute([&counter]() { // 引用捕获 counter
    28 counter++;
    29 std::cout << "Counter incremented to: " << counter << " by thread: " << std::this_thread::get_id() << std::endl;
    30 });
    31
    32 return 0;
    33 }

    代码解析:

    1. 创建 ThreadPoolExecutor:首先,我们创建了一个 folly::ThreadPoolExecutor 实例,指定线程池的大小为 4。ThreadPoolExecutorfolly 库中常用的线程池实现,它基于线程池模式管理一组线程来执行提交的任务。
    2. FOLLY_SCOPE_GUARD:使用 FOLLY_SCOPE_GUARD 确保在 main 函数结束时,executor 会被正确地 shutdown()join(),这是一种良好的资源管理实践,可以防止程序退出时资源泄露或崩溃。
    3. executor.execute([&]() { ... });:这是提交任务的核心代码。executor.execute() 方法接受一个 Lambda 表达式作为参数。
      ▮▮▮▮⚝ [&]():这是一个 Lambda 表达式的声明。[&] 表示以引用捕获所有外部变量(虽然在这个例子中没有捕获任何外部变量),() 表示 Lambda 表达式不接受任何参数。{ ... } 是 Lambda 表达式的函数体,包含了要执行的任务代码。
      ▮▮▮▮⚝ std::cout << ...:Lambda 表达式的函数体中,我们简单地打印一条消息,包含执行任务的线程 ID。std::this_thread::get_id() 用于获取当前线程的 ID。
    4. 提交带参数的 Lambda 表达式:第二个 executor.execute() 调用展示了如何向 Lambda 表达式传递参数。[task_id]() 捕获了外部变量 task_id (值捕获),并在 Lambda 函数体中使用。
    5. 提交捕获外部变量的 Lambda 表达式:第三个 executor.execute() 调用展示了 Lambda 表达式如何通过引用捕获外部变量 counter,并在任务中修改它。

    Lambda 表达式的优势

    简洁性:Lambda 表达式语法简洁,可以快速定义简单的任务逻辑,避免了编写额外的函数或函数对象。
    局部性:Lambda 表达式可以定义在任务提交的地方,代码更加集中,提高了代码的可读性和可维护性。
    捕获外部变量:Lambda 表达式可以方便地捕获外部变量,使得任务可以访问和操作上下文数据。
    类型推导:Lambda 表达式的返回类型可以自动推导,减少了代码的冗余。

    最佳实践

    选择合适的捕获方式:根据任务的需求选择值捕获或引用捕获。值捕获适用于任务只需要读取外部变量值,而引用捕获适用于任务需要修改外部变量的情况。需要注意引用捕获的生命周期问题,确保被捕获的变量在 Lambda 表达式执行时仍然有效。
    避免过度复杂的 Lambda 表达式:对于复杂的任务逻辑,建议将其封装成独立的函数或函数对象,然后在 Lambda 表达式中调用,以保持代码的清晰和可维护性。
    注意 Lambda 表达式的性能:虽然 Lambda 表达式通常具有良好的性能,但在某些极端情况下,过度使用或不当使用 Lambda 表达式可能会引入额外的开销。需要根据实际情况进行性能评估和优化。

    总而言之,Lambda 表达式是向 folly::Executor 提交任务的强大工具,它以其简洁性、灵活性和局部性,极大地提升了并发编程的效率和代码可读性。熟练掌握 Lambda 表达式的使用,可以帮助开发者更加高效地利用 folly::Executor 构建高性能的并发应用。

    6.2 使用 Functor 提交任务 (Submitting Tasks using Functors)

    除了 Lambda 表达式,Functor(仿函数)也是向 folly::Executor 提交任务的常用方式。Functor 本质上是重载了函数调用运算符 operator() 的类或结构体,使得类的对象可以像函数一样被调用。在 C++ 中,Functor 是一种强大的抽象机制,可以用于实现各种可调用对象,包括策略模式、命令模式等。

    Functor 简介

    Functor 的核心在于重载 operator() 运算符。一个类或结构体只要重载了 operator(),它的对象就可以像函数一样使用圆括号 () 进行调用,并执行 operator() 中定义的代码逻辑。

    以下是一个简单的 Functor 示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2
    3 class MyFunctor {
    4 public:
    5 MyFunctor(int id) : id_(id) {}
    6
    7 void operator()() const {
    8 std::cout << "Functor with ID " << id_ << " executed by thread: " << std::this_thread::get_id() << std::endl;
    9 }
    10
    11 private:
    12 int id_;
    13 };
    14
    15 int main() {
    16 MyFunctor functor1(1);
    17 MyFunctor functor2(2);
    18
    19 functor1(); // 像函数一样调用 functor1 对象
    20 functor2(); // 像函数一样调用 functor2 对象
    21
    22 return 0;
    23 }

    代码解析:

    class MyFunctor:定义了一个名为 MyFunctor 的类。
    MyFunctor(int id) : id_(id) {}:构造函数,接受一个 int 类型的参数 id,并初始化成员变量 id_
    void operator()() const { ... }:重载了函数调用运算符 operator()const 关键字表示该 operator() 不会修改对象的状态。函数体中打印一条包含 Functor ID 和线程 ID 的消息。
    functor1();functor2();:在 main 函数中,我们创建了 MyFunctor 的两个对象 functor1functor2,并像调用函数一样调用它们。实际上,这里调用的是 functor1.operator()()functor2.operator()()

    使用 Functor 提交任务

    与 Lambda 表达式类似,Functor 也可以很方便地转换为 folly::Func,因此我们可以使用 Functor 对象向 folly::Executor 提交任务。

    以下代码示例展示了如何使用 Functor 向 folly::ThreadPoolExecutor 提交任务:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/ScopeGuard.h>
    3 #include <iostream>
    4
    5 class TaskFunctor {
    6 public:
    7 TaskFunctor(int task_id) : task_id_(task_id) {}
    8
    9 void operator()() const {
    10 std::cout << "Task Functor " << task_id_ << " executed by thread: " << std::this_thread::get_id() << std::endl;
    11 // 模拟一些耗时操作
    12 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    13 }
    14
    15 private:
    16 int task_id_;
    17 };
    18
    19 int main() {
    20 // 创建一个线程池 Executor,包含 4 个线程
    21 folly::ThreadPoolExecutor executor(4);
    22 // 确保 executor 在 main 函数结束时被销毁
    23 FOLLY_SCOPE_GUARD {
    24 executor.shutdown();
    25 executor.join();
    26 };
    27
    28 // 创建 Functor 对象
    29 TaskFunctor functor1(1);
    30 TaskFunctor functor2(2);
    31 TaskFunctor functor3(3);
    32
    33 // 使用 Functor 对象提交任务
    34 executor.execute(functor1);
    35 executor.execute(functor2);
    36 executor.execute(functor3);
    37
    38 return 0;
    39 }

    代码解析:

    1. class TaskFunctor:定义了一个名为 TaskFunctor 的 Functor 类,与之前的 MyFunctor 类似,但添加了模拟耗时操作的代码 std::this_thread::sleep_for(...)
    2. 创建 TaskFunctor 对象:在 main 函数中,我们创建了三个 TaskFunctor 对象 functor1functor2functor3,分别赋予不同的 task_id_
    3. executor.execute(functor1);:直接将 Functor 对象 functor1 作为参数传递给 executor.execute() 方法。folly::Executor 可以正确地识别并执行 Functor 对象中 operator() 定义的任务逻辑。

    Functor 的优势

    封装性:Functor 可以将任务逻辑和相关的数据封装在一个类中,提高了代码的组织性和可维护性。
    状态保持:Functor 可以通过成员变量来保持状态,这在需要执行有状态的任务时非常有用。例如,Functor 可以记录任务的执行次数、中间结果等。
    代码复用:Functor 可以被多次使用,提交到不同的 Executor 或在不同的上下文中执行,提高了代码的复用性。
    更强的表达能力:相比于简单的 Lambda 表达式,Functor 可以实现更复杂的任务逻辑,例如,可以定义更复杂的构造函数、析构函数、成员函数等。

    Functor 与 Lambda 表达式的比较

    特性Lambda 表达式Functor
    语法简洁,内联定义相对繁琐,需要定义类或结构体
    状态通过捕获列表捕获外部变量通过成员变量保持状态
    封装性较弱,通常用于简单的、局部的任务逻辑强,可以将任务逻辑和数据封装在一个类中
    代码复用较弱,主要用于一次性或少量重复使用的简单任务强,可以被多次复用,适用于复杂的、可复用的任务逻辑
    适用场景简单的、局部的、一次性任务,例如事件处理、回调函数复杂的、有状态的、可复用的任务,例如算法、策略模式

    最佳实践

    合理选择 Functor 或 Lambda 表达式:对于简单的任务,Lambda 表达式通常更加简洁方便;对于复杂的、有状态的、需要复用的任务,Functor 可能更适合。
    保持 Functor 的简洁性:虽然 Functor 可以封装复杂的逻辑,但也应尽量保持其简洁性,避免过度设计。
    注意 Functor 的生命周期:与 Lambda 表达式类似,需要注意 Functor 对象在提交到 Executor 后,其生命周期需要覆盖任务的执行时间。

    总而言之,Functor 是向 folly::Executor 提交任务的另一种重要方式,它以其封装性、状态保持能力和代码复用性,为并发编程提供了更多的选择和灵活性。开发者可以根据任务的特点和需求,灵活选择 Lambda 表达式或 Functor 来提交任务,以达到最佳的代码结构和性能。

    6.3 任务的取消与中断 (Task Cancellation and Interruption)

    在并发编程中,任务的取消(Cancellation)和中断(Interruption)是重要的控制机制。当任务不再需要执行,或者执行时间过长、超出预期时,我们需要能够安全地取消或中断任务的执行,以释放资源、提高效率或响应用户请求。

    任务取消与中断的概念

    任务取消(Cancellation):通常指外部请求停止一个正在执行或等待执行的任务。任务取消通常是协作式的,即任务本身需要检查取消状态,并主动停止执行。
    任务中断(Interruption):通常指强制停止一个正在执行的任务。任务中断可以是异步的,由外部线程或系统信号触发。任务中断通常比取消更加强制,但也可能带来一些风险,例如数据不一致或资源泄露。

    folly::Executor 中,任务的取消和中断主要依赖于协作式取消机制,即任务需要主动检查取消状态并做出响应。folly::Executor 本身并没有提供强制中断任务的 API,这主要是出于安全性和资源管理的考虑。强制中断任务可能会导致任务执行到一半被强行终止,从而可能破坏数据的一致性或导致资源无法正确释放。

    folly::CancellationToken 与协作式取消

    folly 库提供了 folly::CancellationTokenfolly::CancellationTokenSource 类,用于实现协作式取消机制。

    folly::CancellationTokenSource:用于创建和管理取消令牌(folly::CancellationToken)。CancellationTokenSource 可以被外部代码持有,并用于触发取消操作。
    folly::CancellationToken:取消令牌,可以传递给任务。任务可以通过检查取消令牌的状态来判断是否需要停止执行。

    使用 folly::CancellationToken 取消任务

    以下代码示例展示了如何使用 folly::CancellationToken 实现任务的协作式取消:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/ScopeGuard.h>
    3 #include <folly/CancellationToken.h>
    4 #include <folly/CancellationTokenSource.h>
    5 #include <iostream>
    6 #include <chrono>
    7 #include <thread>
    8
    9 void longRunningTask(folly::CancellationToken token, int task_id) {
    10 for (int i = 0; i < 100; ++i) {
    11 // 检查取消状态
    12 if (token.isCancellationRequested()) {
    13 std::cout << "Task " << task_id << " cancelled." << std::endl;
    14 return; // 主动停止任务执行
    15 }
    16 std::cout << "Task " << task_id << " running... iteration " << i << " thread: " << std::this_thread::get_id() << std::endl;
    17 std::this_thread::sleep_for(std::chrono::milliseconds(50));
    18 }
    19 std::cout << "Task " << task_id << " finished." << std::endl;
    20 }
    21
    22 int main() {
    23 folly::ThreadPoolExecutor executor(4);
    24 FOLLY_SCOPE_GUARD {
    25 executor.shutdown();
    26 executor.join();
    27 };
    28
    29 folly::CancellationTokenSource cts;
    30 folly::CancellationToken token = cts.getToken();
    31
    32 // 提交可取消的任务
    33 executor.execute([token]() {
    34 longRunningTask(token, 1);
    35 });
    36
    37 // 提交另一个可取消的任务
    38 executor.execute([token]() {
    39 longRunningTask(token, 2);
    40 });
    41
    42 // 等待一段时间后取消任务
    43 std::this_thread::sleep_for(std::chrono::seconds(1));
    44 std::cout << "Requesting cancellation..." << std::endl;
    45 cts.requestCancellation(); // 请求取消
    46
    47 std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待任务响应取消
    48
    49 return 0;
    50 }

    代码解析:

    1. folly::CancellationTokenSource cts;folly::CancellationToken token = cts.getToken();:创建一个 CancellationTokenSource 对象 cts,并从中获取一个 CancellationToken 对象 tokentoken 将被传递给任务。
    2. longRunningTask(folly::CancellationToken token, int task_id) 函数
      ▮▮▮▮⚝ 接受一个 folly::CancellationToken 对象 token 作为参数。
      ▮▮▮▮⚝ 在循环中,使用 token.isCancellationRequested() 检查是否收到取消请求。如果收到取消请求,则打印取消消息并 return,主动停止任务执行。
      ▮▮▮▮⚝ 如果未收到取消请求,则继续执行任务逻辑(这里是打印消息和休眠)。
    3. executor.execute([token]() { longRunningTask(token, 1); });:使用 Lambda 表达式提交任务,并将 token 传递给 longRunningTask 函数。
    4. cts.requestCancellation();:在主线程中,等待一段时间后,调用 cts.requestCancellation() 方法请求取消与 cts 关联的所有 CancellationToken 对象(这里只有一个 token)。
    5. 任务的协作式取消:当 cts.requestCancellation() 被调用后,token.isCancellationRequested() 将返回 truelongRunningTask 函数在每次循环迭代时检查 token.isCancellationRequested() 的值,一旦发现为 true,就主动停止执行。

    协作式取消的优点与局限性

    优点
    ▮▮▮▮⚝ 安全性:协作式取消是安全的,因为它允许任务在合适的时机(例如,完成当前迭代或清理资源后)停止执行,避免了强制中断可能导致的数据不一致或资源泄露问题。
    ▮▮▮▮⚝ 灵活性:任务可以根据自身的逻辑来决定如何响应取消请求。例如,任务可以在取消前执行一些清理操作。
    ▮▮▮▮⚝ 资源管理:协作式取消可以更好地管理资源,例如,任务可以在取消时释放已占用的内存或文件句柄。

    局限性
    ▮▮▮▮⚝ 依赖任务的配合:协作式取消依赖于任务本身的代码来检查取消状态并做出响应。如果任务没有正确地检查取消状态,或者忽略了取消请求,则取消操作将不会生效。
    ▮▮▮▮⚝ 无法强制中断:协作式取消无法强制中断正在执行的任务。如果任务进入死循环或长时间阻塞,即使请求取消,任务也可能无法停止。

    最佳实践

    在长时间运行的任务中定期检查取消状态:确保在长时间运行的任务中,定期调用 token.isCancellationRequested() 检查取消状态,并及时响应取消请求。
    在循环、I/O 操作等关键点检查取消状态:在循环迭代、I/O 操作等可能耗时较长的操作前后,检查取消状态,以便及时停止任务。
    提供清理操作:在任务取消时,执行必要的清理操作,例如释放资源、回滚事务等,以确保系统的状态一致性和资源安全。
    考虑超时机制:对于可能长时间运行的任务,可以结合超时机制,在任务执行时间超过预设阈值时,自动请求取消,防止任务无限期地运行下去。

    总而言之,folly::CancellationToken 提供了一种安全、灵活的协作式取消机制,用于管理 folly::Executor 提交的任务的生命周期。开发者应该充分利用 CancellationToken,在任务中合理地加入取消检查逻辑,以构建更加健壮、可控的并发应用。

    6.4 任务的优先级管理 (Task Priority Management)

    在某些应用场景中,我们需要对任务进行优先级管理,确保重要的任务能够优先执行,而不太重要的任务可以稍后执行。例如,在响应用户请求的 Web 服务器中,处理用户交互的任务可能需要比后台数据同步任务更高的优先级。

    任务优先级管理的概念

    任务优先级管理是指根据任务的重要性或紧急程度,为任务分配不同的优先级,并在任务调度时,优先执行优先级较高的任务。优先级管理可以提高系统的响应速度和资源利用率,尤其是在高负载或资源有限的环境下。

    folly::Executor 的优先级支持

    folly::Executor 接口本身并没有直接提供任务优先级管理的功能。默认情况下,folly::Executor 的实现(例如 ThreadPoolExecutor)通常按照任务提交的顺序或基于某种公平调度策略来执行任务,而不考虑任务的优先级。

    然而,我们可以通过自定义 Executor 或结合其他 folly 库的组件来实现任务优先级管理。以下是一些可能的实现思路:

    1. 自定义优先级 Executor

    我们可以自定义一个 Executor 实现,该 Executor 内部维护一个优先级队列(Priority Queue)来存储待执行的任务。优先级队列会根据任务的优先级进行排序,保证优先级较高的任务总是被优先取出执行。

    以下是一个简单的自定义优先级 Executor 的概念性示例(简化版,仅供演示思路):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/Executor.h>
    2 #include <folly/Function.h>
    3 #include <folly/ScopeGuard.h>
    4 #include <queue>
    5 #include <memory>
    6 #include <thread>
    7 #include <iostream>
    8
    9 class PriorityTask {
    10 public:
    11 using TaskFunction = folly::Func<void()>;
    12
    13 PriorityTask(int priority, TaskFunction task) : priority_(priority), task_(std::move(task)) {}
    14
    15 int getPriority() const { return priority_; }
    16 void execute() { task_(); }
    17
    18 // 定义优先级比较规则,优先级数值越小,优先级越高
    19 bool operator>(const PriorityTask& other) const {
    20 return priority_ > other.priority_;
    21 }
    22
    23 private:
    24 int priority_;
    25 TaskFunction task_;
    26 };
    27
    28 class PriorityExecutor : public folly::Executor {
    29 public:
    30 PriorityExecutor(int thread_count) : running_(true) {
    31 for (int i = 0; i < thread_count; ++i) {
    32 threads_.emplace_back([this]() {
    33 while (running_) {
    34 std::shared_ptr<PriorityTask> task_ptr;
    35 {
    36 std::unique_lock<std::mutex> lock(mutex_);
    37 condition_.wait(lock, [this]() { return !task_queue_.empty() || !running_; });
    38 if (!running_ && task_queue_.empty()) {
    39 break; // 退出线程
    40 }
    41 task_ptr = task_queue_.top();
    42 task_queue_.pop();
    43 }
    44 if (task_ptr) {
    45 task_ptr->execute();
    46 }
    47 }
    48 });
    49 }
    50 }
    51
    52 ~PriorityExecutor() override {
    53 stop();
    54 }
    55
    56 void execute(folly::Func<void()> func) override {
    57 executeWithPriority(0, func); // 默认优先级为 0
    58 }
    59
    60 void executeWithPriority(int priority, folly::Func<void()> func) {
    61 std::shared_ptr<PriorityTask> task_ptr = std::make_shared<PriorityTask>(priority, std::move(func));
    62 {
    63 std::lock_guard<std::mutex> lock(mutex_);
    64 task_queue_.push(task_ptr);
    65 }
    66 condition_.notify_one(); // 通知等待线程
    67 }
    68
    69 void stop() {
    70 running_ = false;
    71 condition_.notify_all(); // 通知所有等待线程退出
    72 for (auto& thread : threads_) {
    73 thread.join();
    74 }
    75 }
    76
    77 private:
    78 bool running_;
    79 std::vector<std::thread> threads_;
    80 std::mutex mutex_;
    81 std::condition_variable condition_;
    82 std::priority_queue<std::shared_ptr<PriorityTask>, std::vector<std::shared_ptr<PriorityTask>>, std::greater<std::shared_ptr<PriorityTask>>> task_queue_; // 优先级队列
    83 };
    84
    85 int main() {
    86 PriorityExecutor executor(4);
    87 FOLLY_SCOPE_GUARD {
    88 executor.stop();
    89 };
    90
    91 // 提交不同优先级的任务
    92 executor.executeWithPriority(5, []() { std::cout << "Low priority task 1, thread: " << std::this_thread::get_id() << std::endl; });
    93 executor.executeWithPriority(1, []() { std::cout << "High priority task 1, thread: " << std::this_thread::get_id() << std::endl; });
    94 executor.executeWithPriority(3, []() { std::cout << "Medium priority task 1, thread: " << std::this_thread::get_id() << std::endl; });
    95 executor.executeWithPriority(2, []() { std::cout << "High priority task 2, thread: " << std::this_thread::get_id() << std::endl; });
    96 executor.executeWithPriority(4, []() { std::cout << "Medium priority task 2, thread: " << std::this_thread::get_id() << std::endl; });
    97 executor.executeWithPriority(6, []() { std::cout << "Low priority task 2, thread: " << std::this_thread::get_id() << std::endl; });
    98
    99 std::this_thread::sleep_for(std::chrono::seconds(2));
    100
    101 return 0;
    102 }

    代码解析:

    1. PriorityTask
      ▮▮▮▮⚝ 封装了任务的优先级 priority_ 和任务函数 task_
      ▮▮▮▮⚝ 重载了 operator>,用于优先级队列的比较。优先级数值越小,优先级越高。
    2. PriorityExecutor
      ▮▮▮▮⚝ 继承自 folly::Executor
      ▮▮▮▮⚝ 内部使用 std::priority_queue<std::shared_ptr<PriorityTask>, ...> 作为任务队列,存储 PriorityTask 智能指针。
      ▮▮▮▮⚝ executeWithPriority(int priority, folly::Func<void()> func) 方法:创建 PriorityTask 对象,并将其加入优先级队列。
      ▮▮▮▮⚝ 工作线程从优先级队列中取出优先级最高的任务执行。
    3. main 函数
      ▮▮▮▮⚝ 创建 PriorityExecutor 实例。
      ▮▮▮▮⚝ 使用 executor.executeWithPriority() 提交不同优先级的任务。

    2. 结合 folly::PriorityQueueDispatcher

    folly 库本身提供了一个 folly::PriorityQueueDispatcher 类,可以用于实现基于优先级的任务调度。PriorityQueueDispatcher 可以与 folly::Executor 结合使用,将任务分发到不同的 Executor 上,并根据优先级进行调度。

    具体使用方法可以参考 folly::PriorityQueueDispatcher 的文档和示例,这里不再展开详细代码示例。

    3. 基于任务类型的优先级划分

    在某些场景下,我们可以根据任务的类型来划分优先级,例如,将用户请求处理任务设置为高优先级,将后台数据同步任务设置为低优先级。然后,可以使用不同的 Executor 来处理不同类型的任务,并为高优先级任务分配更多的资源(例如,更多的线程)。

    优先级管理的权衡

    优先级反转(Priority Inversion):优先级管理可能导致优先级反转问题,即低优先级任务阻塞了高优先级任务的执行。需要采取措施来避免或缓解优先级反转,例如,优先级继承(Priority Inheritance)或优先级天花板协议(Priority Ceiling Protocol)。
    饥饿(Starvation):如果持续有高优先级任务提交,可能会导致低优先级任务长时间得不到执行,发生饥饿现象。需要合理设置优先级策略,避免过度偏向高优先级任务。
    调度开销:优先级调度通常比简单的 FIFO 调度更复杂,可能会引入一定的调度开销。需要根据实际应用场景和性能需求,权衡优先级管理带来的收益和开销。

    最佳实践

    谨慎使用优先级管理:优先级管理并非在所有场景下都适用。只有在确实需要区分任务重要性,并希望优先处理关键任务的场景下,才考虑使用优先级管理。
    合理设置优先级:根据任务的实际重要性和紧急程度,合理设置任务的优先级。避免过度使用高优先级,导致低优先级任务饥饿。
    监控和调优:对优先级管理系统的性能进行监控和调优,确保优先级管理能够有效地提高系统性能,而不是引入新的问题。
    考虑优先级反转和饥饿问题:在设计优先级管理系统时,需要考虑优先级反转和饥饿问题,并采取相应的措施来避免或缓解这些问题。

    总而言之,虽然 folly::Executor 本身没有直接提供任务优先级管理功能,但我们可以通过自定义 Executor、结合 folly::PriorityQueueDispatcher 或基于任务类型划分等方式来实现任务优先级管理。在实际应用中,需要根据具体的场景和需求,权衡优先级管理的收益和开销,并谨慎地设计和实现优先级管理策略。

    END_OF_CHAPTER

    7. chapter 7: Executor 的高级应用 (Advanced Applications of Executor)

    7.1 自定义 Executor 的实现 (Implementing Custom Executors)

    在之前的章节中,我们学习了 Folly 库提供的多种内置 Executor 实现,例如 ThreadPoolExecutorInlineExecutorIOExecutorCPUThreadPoolExecutor。这些预定义的 Executor 已经能够满足绝大多数并发场景的需求。然而,在某些特定情况下,我们可能需要根据自身的业务特点和性能需求,定制化 Executor 的行为。本节将深入探讨如何自定义 Executor 的实现,从而更好地掌控任务的执行方式和资源利用。

    7.1.1 自定义 Executor 的接口设计 (Interface Design of Custom Executors)

    自定义 Executor 的核心在于理解并实现 folly::Executor 接口。folly::Executor 是一个抽象基类,定义了 Executor 的基本行为规范。任何自定义的 Executor 都必须继承自 folly::Executor 并实现其核心方法。让我们回顾一下 folly::Executor 的关键接口:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class Executor {
    2 public:
    3 virtual ~Executor() = default;
    4
    5 virtual void execute(Func func) = 0;
    6
    7 virtual void schedule(Func func, std::chrono::milliseconds delay) {
    8 // 默认实现,直接调用 execute
    9 execute(std::move(func));
    10 }
    11
    12 virtual KeepAliveToken getKeepAliveToken(std::shared_ptr<Executor> self) {
    13 return KeepAliveToken{std::move(self)};
    14 }
    15 };

    ~Executor() 析构函数 (Destructor): 虚析构函数,确保在销毁 Executor 对象时能够正确地释放资源。通常使用默认实现即可。

    execute(Func func) 方法 (Method): 这是 Executor 接口中最核心的方法,也是必须实现的纯虚函数。它接受一个可调用对象 Func (例如 Lambda 表达式、函数对象等) 作为参数,并将该任务提交给 Executor 执行。具体的执行方式由自定义 Executor 的实现决定,可以是同步执行、异步执行、放入线程池执行等等。

    schedule(Func func, std::chrono::milliseconds delay) 方法 (Method): 该方法用于提交一个延迟执行的任务。它接受一个可调用对象 Func 和一个延迟时间 delay (以毫秒为单位) 作为参数。默认实现是直接调用 execute() 方法,即立即执行任务。如果需要实现真正的延迟执行功能,例如定时任务,则需要在自定义 Executor 中重写此方法。

    getKeepAliveToken(std::shared_ptr<Executor> self) 方法 (Method): 该方法用于获取一个 KeepAliveToken 对象,用于管理 Executor 的生命周期。KeepAliveToken 的主要作用是防止 Executor 在任务执行完成之前被意外销毁。默认实现返回一个持有 Executor 共享指针的 KeepAliveToken。在大多数情况下,默认实现已经足够,无需重写。

    设计自定义 Executor 的关键考虑因素:

    任务执行方式 (Task Execution Method): 自定义 Executor 的核心在于如何实现 execute() 方法。你需要决定任务是同步执行还是异步执行?是使用新的线程执行,还是复用已有的线程?任务的执行顺序是什么?(FIFO, LIFO, 优先级队列等)

    线程管理 (Thread Management): 如果你的 Executor 需要使用线程,你需要考虑线程的创建、销毁、复用和管理策略。例如,是否使用线程池?线程池的大小如何确定?线程的生命周期如何管理?

    任务队列 (Task Queue): 如果任务是异步执行的,你需要一个任务队列来存储待执行的任务。任务队列的选择会直接影响 Executor 的性能和行为。例如,可以使用 std::queue 实现 FIFO 队列,使用 std::priority_queue 实现优先级队列。

    错误处理 (Error Handling): 自定义 Executor 需要考虑如何处理任务执行过程中可能发生的错误。例如,任务抛出异常时,应该如何处理?是否需要通知调用者?

    性能 (Performance): 自定义 Executor 的设计需要兼顾性能。例如,避免不必要的锁竞争,选择高效的数据结构和算法,合理地管理线程资源等。

    7.1.2 实现一个简单的 FIFO Executor (Implementing a Simple FIFO Executor)

    为了更好地理解自定义 Executor 的实现过程,我们来创建一个简单的 FIFO (First-In-First-Out) Executor。这个 Executor 将使用一个线程池来异步执行任务,并使用 std::queue 作为任务队列,按照任务提交的顺序执行。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/Executor.h>
    2 #include <folly/ScopeGuard.h>
    3 #include <folly/Synchronized.h>
    4
    5 #include <deque>
    6 #include <thread>
    7 #include <vector>
    8
    9 class FIFOExecutor : public folly::Executor {
    10 public:
    11 explicit FIFOExecutor(size_t poolSize) : poolSize_(poolSize) {
    12 startThreads();
    13 }
    14
    15 ~FIFOExecutor() override {
    16 stopThreads();
    17 }
    18
    19 void execute(folly::Func func) override {
    20 {
    21 folly::Synchronized<std::deque<folly::Func>>::WriteGuard guard(taskQueue_);
    22 taskQueue_->push_back(std::move(func));
    23 }
    24 taskQueueCV_.notify_one(); // 通知工作线程有新任务
    25 }
    26
    27 private:
    28 void startThreads() {
    29 threads_.reserve(poolSize_);
    30 for (size_t i = 0; i < poolSize_; ++i) {
    31 threads_.emplace_back([this] { threadWorker(); });
    32 }
    33 }
    34
    35 void stopThreads() {
    36 isStopped_ = true;
    37 taskQueueCV_.notify_all(); // 通知所有工作线程退出
    38 for (auto& thread : threads_) {
    39 thread.join();
    40 }
    41 }
    42
    43 void threadWorker() {
    44 while (!isStopped_) {
    45 folly::Func task;
    46 {
    47 folly::Synchronized<std::deque<folly::Func>>::WriteGuard guard(taskQueue_);
    48 taskQueueCV_.wait(guard, [this] { return isStopped_ || !taskQueue_->empty(); });
    49 if (isStopped_ && taskQueue_->empty()) {
    50 break; // 退出线程
    51 }
    52 task = std::move(taskQueue_->front());
    53 taskQueue_->pop_front();
    54 }
    55 if (task) {
    56 task(); // 执行任务
    57 }
    58 }
    59 }
    60
    61 private:
    62 size_t poolSize_;
    63 std::atomic<bool> isStopped_{false};
    64 folly::Synchronized<std::deque<folly::Func>> taskQueue_; // 任务队列
    65 std::condition_variable taskQueueCV_; // 条件变量,用于线程同步
    66 std::vector<std::thread> threads_; // 工作线程池
    67 };

    代码解析:

    FIFOExecutor 类 (Class): 继承自 folly::Executor,表示自定义的 FIFO Executor

    构造函数 FIFOExecutor(size_t poolSize) (Constructor): 接受线程池大小 poolSize 作为参数,并调用 startThreads() 方法启动工作线程。

    析构函数 ~FIFOExecutor() (Destructor): 调用 stopThreads() 方法停止工作线程,并等待所有线程退出。

    execute(folly::Func func) 方法 (Method): 这是 FIFOExecutor 的核心方法。
    ▮▮▮▮⚝ 使用 folly::Synchronized 互斥锁保护任务队列 taskQueue_
    ▮▮▮▮⚝ 将提交的任务 func 加入任务队列的队尾 (push_back)。
    ▮▮▮▮⚝ 调用 taskQueueCV_.notify_one() 通知一个等待中的工作线程有新任务到达。

    startThreads() 方法 (Method): 启动指定数量 (poolSize_) 的工作线程。每个工作线程都执行 threadWorker() 函数。

    stopThreads() 方法 (Method): 停止所有工作线程。
    ▮▮▮▮⚝ 设置 isStopped_ 标志为 true,通知工作线程退出循环。
    ▮▮▮▮⚝ 调用 taskQueueCV_.notify_all() 唤醒所有等待中的工作线程。
    ▮▮▮▮⚝ 循环等待所有工作线程结束 (join)。

    threadWorker() 方法 (Method): 工作线程的主循环。
    ▮▮▮▮⚝ 循环执行直到 isStopped_true
    ▮▮▮▮⚝ 使用 folly::Synchronized 互斥锁保护任务队列 taskQueue_
    ▮▮▮▮⚝ 使用条件变量 taskQueueCV_ 等待新任务到达,或者等待 stopThreads() 的通知。
    ▮▮▮▮⚝ 如果收到停止信号 (isStopped_true 且任务队列为空),则退出循环。
    ▮▮▮▮⚝ 从任务队列的队首 (front) 取出一个任务,并从队列中移除 (pop_front)。
    ▮▮▮▮⚝ 如果取到任务,则执行任务 (task())。

    成员变量 (Member Variables)
    ▮▮▮▮⚝ poolSize_: 线程池大小。
    ▮▮▮▮⚝ isStopped_: 原子布尔变量,用于标记 Executor 是否已停止。
    ▮▮▮▮⚝ taskQueue_: 使用 folly::Synchronized 保护的 std::deque<folly::Func>,作为任务队列。folly::Synchronized 提供了线程安全的互斥访问和内置的 std::deque
    ▮▮▮▮⚝ taskQueueCV_: 条件变量,用于在任务队列为空时,让工作线程进入等待状态,并在新任务到达时被唤醒。
    ▮▮▮▮⚝ threads_: 存储工作线程的 std::vector<std::thread>

    使用示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <iostream>
    2 #include <chrono>
    3
    4 int main() {
    5 FIFOExecutor executor(4); // 创建一个包含 4 个线程的 FIFOExecutor
    6
    7 for (int i = 0; i < 10; ++i) {
    8 executor.execute([i] {
    9 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    10 std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
    11 });
    12 }
    13
    14 std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待任务执行完成
    15 return 0;
    16 }

    这个简单的 FIFOExecutor 演示了如何自定义 Executor,并展示了线程池、任务队列、条件变量等并发编程的基本要素在 Executor 实现中的应用。在实际应用中,你可以根据具体需求,扩展和优化这个 FIFOExecutor,例如添加任务优先级、任务取消、更复杂的调度策略等等。

    7.2 Executor 的组合与链式调用 (Composition and Chaining of Executors)

    在复杂的并发场景中,我们可能需要将多个 Executor 组合起来,形成更强大的执行流程。Executor 的组合和链式调用可以帮助我们构建灵活且高效的并发处理管道。

    Executor 组合 (Executor Composition): 指的是将多个 Executor 组合成一个新的 Executor,新 Executor 的行为是组合后的多个 Executor 行为的叠加或协调。

    Executor 链式调用 (Executor Chaining): 指的是在一个 Executor 上提交的任务完成后,自动将结果传递给另一个 Executor 继续处理,形成任务处理链。

    Executor 组合的示例:

    假设我们需要先使用 IOExecutor 处理 I/O 密集型任务,然后将处理结果交给 CPUThreadPoolExecutor 进行 CPU 密集型计算。我们可以创建一个组合 Executor,将 IOExecutorCPUThreadPoolExecutor 封装在一起。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 class IOAndCPUExecutor : public folly::Executor {
    2 public:
    3 IOAndCPUExecutor(std::shared_ptr<folly::IOExecutor> ioExecutor,
    4 std::shared_ptr<folly::CPUThreadPoolExecutor> cpuExecutor)
    5 : ioExecutor_(std::move(ioExecutor)), cpuExecutor_(std::move(cpuExecutor)) {}
    6
    7 void execute(folly::Func func) override {
    8 ioExecutor_->execute([this, func = std::move(func)]() mutable {
    9 // 在 IOExecutor 中执行 I/O 密集型任务
    10 // ... I/O 操作 ...
    11
    12 cpuExecutor_->execute(std::move(func)); // 将任务提交给 CPUExecutor 执行 CPU 密集型任务
    13 });
    14 }
    15
    16 private:
    17 std::shared_ptr<folly::IOExecutor> ioExecutor_;
    18 std::shared_ptr<folly::CPUThreadPoolExecutor> cpuExecutor_;
    19 };

    在这个例子中,IOAndCPUExecutor 接收两个 Executor 作为成员变量:ioExecutor_cpuExecutor_。当 IOAndCPUExecutorexecute() 方法被调用时,它首先在 ioExecutor_ 上提交一个 Lambda 任务。这个 Lambda 任务内部会先执行 I/O 密集型操作,然后将原始任务 func 提交给 cpuExecutor_ 执行 CPU 密集型计算。这样就实现了 I/O 任务和 CPU 任务的流水线处理。

    Executor 链式调用的示例:

    Executor 的链式调用通常与 folly::Futurethen() 方法结合使用。then() 方法允许我们在一个 Future 完成后,继续在另一个 Executor 上执行后续操作。

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <folly/executors/CPUThreadPoolExecutor.h>
    3 #include <folly/futures/Future.h>
    4 #include <folly/experimental/coro/BlockingWait.h>
    5
    6 folly::Future<int> ioTask(std::shared_ptr<folly::Executor> executor) {
    7 return folly::futures::schedule(executor, []() {
    8 std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟 I/O 操作
    9 std::cout << "IO Task executed by thread " << std::this_thread::get_id() << std::endl;
    10 return 10;
    11 });
    12 }
    13
    14 folly::Future<int> cpuTask(std::shared_ptr<folly::Executor> executor, int input) {
    15 return folly::futures::schedule(executor, [input]() {
    16 std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟 CPU 计算
    17 std::cout << "CPU Task executed by thread " << std::this_thread::get_id() << ", input = " << input << std::endl;
    18 return input * 2;
    19 });
    20 }
    21
    22 int main() {
    23 auto ioExecutor = std::make_shared<folly::IOExecutor>();
    24 auto cpuExecutor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
    25
    26 folly::Future<int> resultFuture = ioTask(ioExecutor)
    27 .then(folly::InlineExecutor::instance(),
    28 [&](int ioResult) { // 使用 InlineExecutor 在当前线程继续处理
    29 return cpuTask(cpuExecutor, ioResult); // 将结果传递给 cpuTask
    30 });
    31
    32 int result = folly::coro::blockingWait(resultFuture); // 阻塞等待 Future 完成
    33
    34 std::cout << "Final result: " << result << std::endl;
    35
    36 return 0;
    37 }

    在这个例子中:

    ioTask() 函数使用 folly::futures::schedule()IOExecutor 上提交一个 I/O 密集型任务,并返回一个 folly::Future<int> 代表任务的结果。

    cpuTask() 函数类似,使用 folly::futures::schedule()CPUThreadPoolExecutor 上提交一个 CPU 密集型任务,并返回一个 folly::Future<int>

    ③ 在 main() 函数中,我们首先调用 ioTask(ioExecutor) 获取一个 Future。然后使用 .then() 方法链式调用,指定当 ioTask 完成后,在 folly::InlineExecutor::instance() (即当前线程) 上执行一个 Lambda 函数。这个 Lambda 函数接收 ioTask 的结果 ioResult 作为输入,并调用 cpuTask(cpuExecutor, ioResult) 将结果传递给 cpuTask 继续处理。

    ④ 最终,resultFuture 代表了整个链式调用的结果。我们使用 folly::coro::blockingWait() 阻塞等待 resultFuture 完成,并获取最终结果。

    通过 Executor 的组合和链式调用,我们可以构建复杂的并发处理流程,将不同类型的任务分配到最合适的 Executor 上执行,从而最大化资源利用率和程序性能。

    7.3 Executor 与 Future/Promise 的协同工作 (Collaboration of Executor with Future/Promise)

    ExecutorFuture/Promise 是并发编程中一对重要的伙伴,它们协同工作,共同构建异步编程模型。

    Executor 负责任务的调度和执行 (Task Scheduling and Execution)Executor 接收任务 (通常是可调用对象),并决定在哪个线程或线程池中执行这些任务。它关注的是任务的执行机制和资源管理。

    Future/Promise 负责异步结果的传递和同步 (Asynchronous Result Passing and Synchronization)Promise 允许我们在异步任务中设置结果或异常,而 Future 则允许我们在任务完成时获取结果或处理异常。Future/Promise 关注的是异步操作的结果和状态管理。

    协同工作机制 (Collaboration Mechanism)

    任务提交 (Task Submission): 当我们使用 folly::futures::schedule(executor, func) 提交一个任务时,folly::futures::schedule() 会创建一个 Promise 和一个 Future,并将任务 func 提交给指定的 Executor 执行。Future 对象会被立即返回给调用者,而 Promise 对象则会被传递给异步任务内部。

    异步执行 (Asynchronous Execution)Executor 在其管理的线程中执行提交的任务 func。任务 func 内部可以调用 PromisesetValue()setException() 方法来设置任务的结果或异常。

    结果获取与同步 (Result Retrieval and Synchronization): 调用者可以通过 Future 对象来获取异步任务的结果。Future 提供了多种方法来等待任务完成并获取结果,例如 get() (阻塞等待)、then() (链式调用)、wait() (非阻塞等待) 等。

    代码示例:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/futures/Future.h>
    3 #include <folly/experimental/coro/BlockingWait.h>
    4 #include <iostream>
    5
    6 folly::Future<int> asyncTask(std::shared_ptr<folly::Executor> executor, int input) {
    7 return folly::futures::schedule(executor, [input]() {
    8 std::this_thread::sleep_for(std::chrono::milliseconds(300)); // 模拟异步操作
    9 std::cout << "Async task executed by thread " << std::this_thread::get_id() << ", input = " << input << std::endl;
    10 return input * input;
    11 });
    12 }
    13
    14 int main() {
    15 auto executor = std::make_shared<folly::ThreadPoolExecutor>(4);
    16
    17 folly::Future<int> future1 = asyncTask(executor, 5);
    18 folly::Future<int> future2 = asyncTask(executor, 10);
    19
    20 folly::Future<std::pair<int, int>> combinedFuture = folly::futures::collect(future1, future2); // 同时等待多个 Future
    21
    22 std::pair<int, int> results = folly::coro::blockingWait(combinedFuture); // 阻塞等待所有 Future 完成
    23
    24 std::cout << "Result 1: " << results.first << ", Result 2: " << results.second << std::endl;
    25
    26 return 0;
    27 }

    代码解析:

    asyncTask() 函数使用 folly::futures::schedule() 在指定的 Executor 上提交一个异步任务,并返回一个 folly::Future<int>

    ② 在 main() 函数中,我们创建了一个 ThreadPoolExecutor,并使用 asyncTask() 提交了两个异步任务 future1future2

    folly::futures::collect(future1, future2) 函数将多个 Future 合并成一个新的 Future (combinedFuture),当所有输入的 Future 都完成时,combinedFuture 才会完成,其结果是一个包含所有输入 Future 结果的 std::pair

    folly::coro::blockingWait(combinedFuture) 阻塞等待 combinedFuture 完成,并获取所有异步任务的结果。

    通过 ExecutorFuture/Promise 的协同工作,我们可以方便地进行异步任务的提交、执行、结果获取和同步,构建高效的异步并发程序。Executor 负责任务的执行,Future/Promise 负责结果的管理,两者分工合作,共同实现了强大的异步编程模型。

    7.4 Executor 在异步编程中的应用 (Application of Executor in Asynchronous Programming)

    Executor 在异步编程中扮演着至关重要的角色,它是异步任务执行的基础设施。异步编程的核心思想是将耗时操作 (例如 I/O 操作、网络请求、CPU 密集型计算等) 放在后台线程或线程池中异步执行,从而避免阻塞主线程,提高程序的响应性和并发性能。Executor 正是实现这一目标的关键工具。

    Executor 在异步编程中的主要应用场景:

    I/O 密集型任务处理 (Handling I/O-Intensive Tasks): 对于网络服务器、文件处理、数据库操作等 I/O 密集型应用,使用 IOExecutor 可以将 I/O 操作提交到专门的 I/O 线程池中异步执行,避免阻塞事件循环线程或主线程,提高服务器的并发处理能力。例如,在网络编程中,可以使用 IOExecutor 处理 socket 的读写事件,非阻塞地处理大量的并发连接。

    CPU 密集型任务卸载 (Offloading CPU-Intensive Tasks): 对于图像处理、科学计算、音视频编解码等 CPU 密集型应用,使用 CPUThreadPoolExecutor 可以将 CPU 计算任务卸载到独立的 CPU 线程池中并行执行,充分利用多核 CPU 的计算能力,缩短任务的执行时间,提高程序的整体性能。例如,在图形渲染引擎中,可以使用 CPUThreadPoolExecutor 并行计算场景中的光照、阴影等效果。

    定时任务和延迟任务 (Scheduled and Delayed Tasks)Executorschedule() 方法可以用于提交延迟执行的任务,实现定时任务和延迟任务的功能。例如,可以使用 ThreadPoolExecutor 或自定义的定时 Executor 来执行周期性的数据清理、心跳检测、缓存刷新等任务。

    异步任务编排和流程控制 (Asynchronous Task Orchestration and Flow Control): 结合 Future/PromiseExecutor 可以用于构建复杂的异步任务编排和流程控制逻辑。例如,可以使用 folly::futures::then()folly::futures::collect()folly::futures::select() 等方法,将多个异步任务串联、并行或选择执行,实现复杂的异步工作流。

    自定义并发策略 (Custom Concurrency Strategies): 通过自定义 Executor,我们可以实现各种特定的并发策略,满足不同应用场景的需求。例如,可以实现优先级队列 Executor,根据任务的优先级进行调度;可以实现限制并发度的 Executor,控制同时执行的任务数量;可以实现远程执行 Executor,将任务分发到远程服务器执行等等。

    异步编程的最佳实践:

    选择合适的 Executor 类型 (Choose the Right Executor Type): 根据任务的类型 (I/O 密集型、CPU 密集型、延迟任务等) 选择最合适的 Executor 类型。例如,I/O 密集型任务使用 IOExecutor,CPU 密集型任务使用 CPUThreadPoolExecutor,简单任务可以使用 InlineExecutor,定时任务可以自定义定时 Executor

    合理配置线程池大小 (Configure Thread Pool Size Appropriately): 线程池的大小直接影响 Executor 的性能。对于 I/O 密集型任务,线程池大小可以适当增大,通常设置为 CPU 核心数的 2 倍甚至更高。对于 CPU 密集型任务,线程池大小通常设置为 CPU 核心数或略小于 CPU 核心数。需要根据实际应用场景和性能测试结果进行调整。

    避免在异步任务中阻塞 (Avoid Blocking in Asynchronous Tasks): 异步任务的设计原则是避免阻塞。在异步任务中,应该尽量使用非阻塞的 I/O 操作和同步机制。如果必须执行阻塞操作,应该将其放在独立的线程中执行,避免阻塞 Executor 的工作线程。

    正确处理异步任务的异常 (Handle Exceptions in Asynchronous Tasks Correctly): 异步任务中可能发生各种异常,需要正确地捕获和处理这些异常,避免程序崩溃或资源泄漏。可以使用 Future 的异常处理机制 (例如 .thenError(), .handleError()) 来处理异步任务的异常。

    监控和调优 Executor 的性能 (Monitor and Tune Executor Performance): 对于性能敏感的应用,需要监控 Executor 的性能指标 (例如任务队列长度、线程池利用率、任务执行时间等),并根据监控结果进行调优,例如调整线程池大小、优化任务调度策略、改进任务代码等。

    总而言之,Executor 是异步编程的核心组件,它提供了灵活、高效的任务调度和执行机制,帮助我们构建高性能、高并发的异步应用程序。深入理解和熟练运用 Executor,是掌握异步编程的关键一步。

    END_OF_CHAPTER

    8. chapter 8: Executor 的性能调优与监控 (Performance Tuning and Monitoring of Executor)

    8.1 Executor 的性能指标 (Performance Metrics of Executor)

    在并发编程中,Executor 扮演着至关重要的角色,它负责任务的调度和执行,直接影响着程序的性能和效率。为了有效地使用和优化 Executor,我们需要理解和监控其关键的性能指标(Performance Metrics)。这些指标能够帮助我们评估 Executor 的运行状态,发现潜在的瓶颈,并指导我们进行合理的调优。

    以下是一些 Executor 性能调优与监控中常用的关键指标:

    吞吐量(Throughput): 吞吐量是指在单位时间内 Executor 完成的任务数量。它是衡量 Executor 处理能力的重要指标。高吞吐量意味着 Executor 能够高效地处理大量的任务请求。吞吐量通常以 每秒任务数 (Tasks per Second, TPS)每分钟任务数 (Tasks per Minute, TPM) 来衡量。

    延迟(Latency): 延迟是指从任务提交到任务完成所花费的时间。在 Executor 的上下文中,延迟可以细分为:
    ▮▮▮▮ⓑ 提交延迟(Submission Latency): 任务提交到 Executor 到任务开始执行之间的时间。这部分延迟可能受到任务队列的长度、调度器的调度策略等因素的影响。
    ▮▮▮▮ⓒ 执行延迟(Execution Latency): 任务实际执行所花费的时间。这部分延迟主要取决于任务本身的复杂度和执行环境的性能。
    ▮▮▮▮ⓓ 完成延迟(Completion Latency): 从任务提交到任务完成的完整时间,是提交延迟和执行延迟的总和。
    低延迟通常是高性能系统的关键指标,尤其是在对响应时间有严格要求的场景中,例如在线服务和实时系统。延迟通常以 毫秒 (Milliseconds, ms)微秒 (Microseconds, μs) 来衡量。

    利用率(Utilization): 利用率是指 Executor 中工作线程的繁忙程度。高利用率通常意味着 Executor 资源得到了充分利用,但过高的利用率也可能导致资源竞争和性能下降。利用率通常以 百分比 (%) 来表示,例如 CPU 利用率或线程利用率。

    饱和度(Saturation): 饱和度是指 Executor 达到其最大处理能力的程度。当 Executor 处于饱和状态时,任务队列可能会堆积,导致延迟增加。饱和度可以帮助我们判断 Executor 是否过载,以及是否需要增加资源或调整配置。饱和度通常通过监控任务队列的长度和拒绝任务的数量来评估。

    任务队列长度(Task Queue Length): 任务队列长度反映了待执行任务的积压程度。过长的任务队列可能意味着 Executor 的处理能力不足,或者任务提交速度过快。监控任务队列长度可以帮助我们及时发现潜在的性能瓶颈。

    拒绝任务数(Rejected Tasks Count): 拒绝任务数是指由于 Executor 达到其最大容量或资源限制而被拒绝执行的任务数量。拒绝任务数过高通常表示 Executor 配置不合理,无法满足任务需求,需要调整 Executor 的配置或增加资源。

    上下文切换次数(Context Switch Count): 上下文切换是指操作系统在不同线程之间切换执行的过程。过多的上下文切换会消耗额外的系统资源,降低程序的整体性能。在线程池 Executor 中,频繁的上下文切换可能成为性能瓶颈之一。

    理解这些性能指标对于有效地监控和调优 Executor 至关重要。在实际应用中,我们需要根据具体的业务场景和性能需求,选择合适的指标进行监控,并根据监控结果进行相应的调优策略调整。例如,对于 CPU 密集型任务,我们可能更关注吞吐量和利用率;而对于 I/O 密集型任务,延迟可能更为关键。

    8.2 线程池大小的动态调整 (Dynamic Adjustment of Thread Pool Size)

    线程池(Thread Pool)是 Executor 模式中最常用的实现之一。线程池的大小,即线程池中工作线程的数量,直接影响着 Executor 的性能。线程池大小设置过小,会导致任务排队等待,无法充分利用系统资源;线程池大小设置过大,则可能导致过多的上下文切换,反而降低性能。因此,动态调整线程池大小(Dynamic Adjustment of Thread Pool Size) 成为了一种重要的性能优化手段。

    动态调整线程池大小的核心思想是根据系统的负载情况和性能指标,自动地增加或减少线程池中的线程数量,以适应不断变化的任务需求,从而在保证性能的同时,最大限度地利用系统资源。

    动态调整线程池大小的策略通常可以分为以下几种:

    基于阈值的调整策略(Threshold-based Adjustment Strategy): 这种策略预先设定一些性能指标的阈值,例如 CPU 利用率、任务队列长度、延迟等。当监控到某个指标超过或低于设定的阈值时,就触发线程池大小的调整。

    例如,可以设置 CPU 利用率的上限和下限。当 CPU 利用率持续高于上限时,可以增加线程池大小;当 CPU 利用率持续低于下限时,可以减少线程池大小。同样,也可以根据任务队列的长度来调整线程池大小。当任务队列长度超过一定阈值时,增加线程池大小;当任务队列长度低于一定阈值时,减少线程池大小。

    基于反馈控制的调整策略(Feedback Control-based Adjustment Strategy): 这种策略将线程池大小的调整视为一个反馈控制系统。系统会定期采集性能指标,例如延迟、吞吐量等,然后根据这些指标与期望值的偏差,计算出需要调整的线程数量,并进行相应的调整。PID 控制器(比例-积分-微分控制器,Proportional-Integral-Derivative Controller)是一种常用的反馈控制算法,可以应用于线程池大小的动态调整。

    基于机器学习的预测策略(Machine Learning-based Prediction Strategy): 这种策略利用机器学习算法,例如时间序列预测模型,对未来的负载情况进行预测。根据预测结果,提前调整线程池的大小,以应对即将到来的负载高峰或低谷。这种策略可以更智能地进行线程池大小的调整,但实现复杂度也相对较高。

    在 Folly 的 ThreadPoolExecutor 中,虽然没有直接提供内置的动态调整线程池大小的功能,但我们可以通过其提供的 API 和监控指标,结合上述策略,自行实现动态调整的功能。例如,我们可以使用 getTaskQueueSize() 方法获取当前任务队列的长度,使用 getPoolStats() 方法获取线程池的统计信息,然后根据这些信息,调用 setNumThreads() 方法动态调整线程池的大小。

    以下是一个基于阈值的动态调整线程池大小的简单示例代码(伪代码):

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 // 假设 threadPoolExecutor 是一个 folly::ThreadPoolExecutor 实例
    2
    3 void monitorThreadPool() {
    4 while (true) {
    5 std::this_thread::sleep_for(std::chrono::seconds(5)); // 每隔 5 秒监控一次
    6
    7 auto queueSize = threadPoolExecutor->getTaskQueueSize();
    8 auto poolStats = threadPoolExecutor->getPoolStats();
    9 double cpuUtilization = getSystemCPUUtilization(); // 获取系统 CPU 利用率,需要自行实现
    10
    11 int currentThreads = poolStats.numThreads;
    12 int targetThreads = currentThreads;
    13
    14 if (cpuUtilization > highCpuThreshold && queueSize > highQueueThreshold) {
    15 targetThreads = std::min(maxThreads, currentThreads + threadIncrement); // 增加线程
    16 } else if (cpuUtilization < lowCpuThreshold && queueSize < lowQueueThreshold && currentThreads > minThreads) {
    17 targetThreads = std::max(minThreads, currentThreads - threadDecrement); // 减少线程
    18 }
    19
    20 if (targetThreads != currentThreads) {
    21 threadPoolExecutor->setNumThreads(targetThreads); // 动态调整线程池大小
    22 LOG(INFO) << "ThreadPool size adjusted from " << currentThreads << " to " << targetThreads;
    23 }
    24 }
    25 }

    在这个示例中,我们定期监控 CPU 利用率和任务队列长度。当 CPU 利用率和任务队列长度都超过设定的高阈值时,增加线程池大小;当 CPU 利用率和任务队列长度都低于设定的低阈值,并且当前线程数大于最小值时,减少线程池大小。通过这种方式,可以实现简单的动态线程池大小调整。

    需要注意的是,动态调整线程池大小并非总是能够带来性能提升。在某些情况下,频繁的线程池大小调整可能会引入额外的开销,例如线程的创建和销毁开销,以及调整过程中的性能抖动。因此,在实际应用中,需要根据具体的业务场景和性能需求,仔细评估动态调整线程池大小的收益和成本,并选择合适的调整策略和参数。

    8.3 任务队列的选择与优化 (Selection and Optimization of Task Queue)

    任务队列(Task Queue)是 Executor 模式中用于存储待执行任务的重要组件。任务队列的选择和优化直接影响着 Executor 的性能和行为。不同的任务队列具有不同的特性,适用于不同的应用场景。

    常见的任务队列类型包括:

    FIFO 队列(First-In, First-Out Queue): FIFO 队列,即先进先出队列,是最常见的任务队列类型。任务按照提交的顺序进入队列,也按照提交的顺序被执行。std::queuefolly::MPMCQueue 等都是 FIFO 队列的实现。FIFO 队列的优点是公平性好,实现简单;缺点是在某些场景下可能不是最优的选择,例如当任务具有优先级差异时。

    LIFO 队列(Last-In, First-Out Queue): LIFO 队列,即后进先出队列,任务按照提交的顺序进入队列,但后提交的任务先被执行。std::stack 可以作为 LIFO 队列的实现。LIFO 队列在某些特定场景下可能有用,例如在深度优先搜索算法中。但在大多数并发场景下,FIFO 队列更为常用。

    优先级队列(Priority Queue): 优先级队列允许为任务设置优先级,优先级高的任务会优先被执行。std::priority_queuefolly::PriorityQueue 等都是优先级队列的实现。优先级队列适用于需要区分任务重要程度的场景,例如在服务质量 (QoS) 保障系统中,需要优先处理高优先级的请求。

    有界队列(Bounded Queue)与无界队列(Unbounded Queue): 根据队列的容量是否有限制,任务队列可以分为有界队列和无界队列。
    ▮▮▮▮ⓑ 有界队列:有界队列具有固定的容量限制。当队列已满时,新的任务提交会被拒绝或阻塞,直到队列中有空间可用。有界队列可以防止任务无限堆积,避免内存溢出等问题,但也可能导致任务拒绝或阻塞,影响系统的响应性。
    ▮▮▮▮ⓒ 无界队列:无界队列没有容量限制,可以容纳任意数量的任务。无界队列的优点是可以接受所有提交的任务,不会拒绝任务;缺点是如果任务提交速度持续超过处理速度,任务队列可能会无限增长,最终导致内存溢出。

    在 Folly 的 ThreadPoolExecutor 中,默认使用的是无界 FIFO 队列 folly::MPMCQueueMPMCQueue (多生产者多消费者队列,Multiple Producer Multiple Consumer Queue) 是一种高性能的并发队列,具有良好的吞吐量和低延迟。

    在选择任务队列时,需要根据具体的应用场景和性能需求进行权衡:

    对于 CPU 密集型任务,通常使用无界 FIFO 队列即可。因为 CPU 密集型任务的执行时间较长,任务提交速度相对较慢,任务队列不太容易堆积。无界队列可以保证所有提交的任务都能被接受,避免任务拒绝。
    对于 I/O 密集型任务,可以考虑使用有界队列。因为 I/O 密集型任务的执行时间较短,任务提交速度可能很快,容易造成任务队列堆积。有界队列可以限制任务队列的长度,防止内存溢出,并提供一定的背压 (Backpressure) 机制,防止系统过载。
    对于需要区分任务优先级的场景,可以使用优先级队列。例如,在网络服务器中,可以为不同的请求设置不同的优先级,优先处理重要的请求。

    除了选择合适的任务队列类型外,还可以通过一些优化手段来提升任务队列的性能:

    选择高性能的并发队列实现:例如 Folly 的 MPMCQueueConcurrentLinkedQueue 等,这些队列针对并发场景进行了优化,具有更高的吞吐量和更低的延迟。
    合理设置有界队列的容量:有界队列的容量设置过小,容易导致任务拒绝;容量设置过大,又可能失去有界队列的优势。需要根据实际的负载情况和性能需求,选择合适的队列容量。
    避免在任务队列中存储过大的任务对象:任务对象越大,队列的内存占用越高,拷贝开销也越大。可以考虑将任务对象瘦身,或者使用指针或引用来传递任务。

    总之,任务队列的选择和优化是 Executor 性能调优的重要环节。需要根据具体的应用场景和性能需求,选择合适的任务队列类型和实现,并进行相应的优化,才能充分发挥 Executor 的性能优势。

    8.4 使用 Folly PerfCounter 进行性能监控 (Performance Monitoring using Folly PerfCounter)

    Folly PerfCounter 是 Folly 库提供的一个强大的性能计数器(Performance Counter)库,用于收集和监控程序的性能指标。PerfCounter 可以方便地记录各种类型的计数器,例如计数、平均值、最大值、最小值、直方图等,并支持多种输出格式,例如日志、JSON、Graphite 等。在 Executor 的性能监控中,PerfCounter 可以发挥重要的作用。

    使用 Folly PerfCounter 进行 Executor 性能监控的基本步骤如下:

    定义性能计数器:首先,需要定义需要监控的性能计数器。PerfCounter 提供了多种计数器类型,可以根据需要选择合适的类型。例如,可以使用 SumCount 记录任务完成数量,使用 AvgCount 记录平均延迟,使用 MaxCount 记录最大延迟,使用 HistogramCount 记录延迟的分布情况等。

    在代码中更新计数器:在 Executor 的相关代码中,例如任务提交前后、任务执行开始和结束时,更新相应的性能计数器。可以使用 PerfCounter::add() 方法增加计数器的值,使用 PerfCounter::set() 方法设置计数器的值,使用 PerfCounter::start()PerfCounter::stop() 方法记录时间间隔等。

    配置计数器输出:配置 PerfCounter 的输出方式和输出频率。可以将计数器数据输出到日志文件、JSON 文件、Graphite 服务器等。可以设置定期输出计数器数据,例如每秒、每分钟或每小时输出一次。

    分析计数器数据:收集 PerfCounter 输出的计数器数据,并进行分析。可以使用各种工具和方法对数据进行可视化、统计分析、趋势分析等,从而了解 Executor 的性能状况,发现潜在的性能瓶颈,并指导性能调优。

    以下是一个使用 Folly PerfCounter 监控 ThreadPoolExecutor 性能的示例代码:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/perfcounter/PerfCounter.h>
    3 #include <folly/logging/xlog.h>
    4
    5 #include <chrono>
    6 #include <iostream>
    7
    8 using namespace folly;
    9
    10 // 定义性能计数器
    11 PerfCounter taskSubmitCount{"executor.task.submit.count", PerfCounter::SumCount};
    12 PerfCounter taskCompleteCount{"executor.task.complete.count", PerfCounter::SumCount};
    13 PerfCounter taskLatencyAvg{"executor.task.latency.avg", PerfCounter::AvgCount};
    14 PerfCounter taskLatencyMax{"executor.task.latency.max", PerfCounter::MaxCount};
    15 PerfCounter taskQueueSizeGauge{"executor.task.queue.size", PerfCounter::Gauge};
    16 PerfCounter threadPoolSizeGauge{"executor.threadpool.size", PerfCounter::Gauge};
    17 PerfCounter activeThreadsGauge{"executor.threadpool.active_threads", PerfCounter::Gauge};
    18
    19 int main() {
    20 // 创建 ThreadPoolExecutor
    21 ThreadPoolExecutor executor{4};
    22
    23 // 定期更新 Gauge 类型的计数器
    24 auto updateGauges = [&]() {
    25 while (true) {
    26 taskQueueSizeGauge.set(executor.getTaskQueueSize());
    27 threadPoolSizeGauge.set(executor.getPoolStats().numThreads);
    28 activeThreadsGauge.set(executor.getPoolStats().activeThreads);
    29 std::this_thread::sleep_for(std::chrono::seconds(1));
    30 }
    31 };
    32 std::thread gaugeThread(updateGauges);
    33 gaugeThread.detach();
    34
    35 // 提交任务并监控性能
    36 for (int i = 0; i < 100; ++i) {
    37 taskSubmitCount.add(1); // 任务提交计数 +1
    38 auto startTime = std::chrono::high_resolution_clock::now();
    39 executor.execute([i, startTime]() {
    40 std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 模拟任务执行
    41 auto endTime = std::chrono::high_resolution_clock::now();
    42 auto latency = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
    43 taskCompleteCount.add(1); // 任务完成计数 +1
    44 taskLatencyAvg.addValue(latency); // 记录平均延迟
    45 taskLatencyMax.setMaxValue(latency); // 记录最大延迟
    46 XLOG(INFO) << "Task " << i << " completed, latency = " << latency << "ms";
    47 });
    48 }
    49
    50 // 等待所有任务完成
    51 executor.join();
    52
    53 // 输出性能计数器数据 (示例,实际应用中可以配置更丰富的输出方式)
    54 std::cout << "Task Submit Count: " << taskSubmitCount.sum() << std::endl;
    55 std::cout << "Task Complete Count: " << taskCompleteCount.sum() << std::endl;
    56 std::cout << "Average Task Latency: " << taskLatencyAvg.value() << "ms" << std::endl;
    57 std::cout << "Max Task Latency: " << taskLatencyMax.maxValue() << "ms" << std::endl;
    58 std::cout << "Max Task Queue Size: " << taskQueueSizeGauge.maxValue() << std::endl;
    59 std::cout << "ThreadPool Size: " << threadPoolSizeGauge.value() << std::endl;
    60 std::cout << "Active Threads: " << activeThreadsGauge.value() << std::endl;
    61
    62 return 0;
    63 }

    在这个示例代码中,我们定义了多个 PerfCounter 计数器,分别用于监控任务提交数量、任务完成数量、平均延迟、最大延迟、任务队列长度、线程池大小和活跃线程数。在任务提交和完成时,更新相应的计数器。同时,使用一个独立的线程定期更新 Gauge 类型的计数器,例如任务队列长度和线程池大小。最后,输出计数器数据到标准输出。

    通过 Folly PerfCounter,我们可以方便地收集和监控 Executor 的性能指标,为性能分析和调优提供有力的数据支持。在实际应用中,可以根据需要定义更多的计数器,并配置更丰富的输出方式,例如将数据输出到 Graphite 或 Prometheus 等监控系统,进行更全面的性能监控和分析。

    END_OF_CHAPTER

    9. chapter 9: 实战案例分析 (Practical Case Study Analysis)

    9.1 案例一:构建高并发网络服务器 (Case Study 1: Building a High-Concurrency Network Server)

    在现代互联网应用中,构建能够处理高并发请求的网络服务器是一项核心挑战。高并发网络服务器需要能够同时处理大量的客户端连接,并快速响应用户的请求。传统的多线程或多进程模型在面对极高的并发量时,往往会因为线程/进程切换的开销和资源竞争而效率低下。Executor 模式,特别是 Folly 库提供的 IOExecutorCPUThreadPoolExecutor,为构建高性能、高并发的网络服务器提供了强大的工具。

    9.1.1 使用 IOExecutor 处理网络 I/O (Using IOExecutor to Handle Network I/O)

    网络服务器的核心任务之一是处理网络 I/O 操作,例如接收客户端连接、读取客户端请求、发送服务器响应等。这些 I/O 操作通常是阻塞的,如果使用传统的同步阻塞 I/O 模型,服务器在等待 I/O 操作完成时会被阻塞,无法处理其他客户端的请求,导致并发性能下降。

    IOExecutor 的设计目标就是为了高效地处理 I/O 密集型任务。它通常与非阻塞 I/O (Non-blocking I/O) 和事件驱动 (Event-driven) 机制结合使用,例如 epollkqueue 等。IOExecutor 负责在 I/O 事件就绪时,调度相应的任务来处理 I/O 操作,从而避免线程阻塞,提高服务器的并发处理能力。

    以下代码示例展示了如何使用 IOExecutor 处理网络连接的接受:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <folly/io/async/AsyncServerSocket.h>
    3 #include <folly/io/async/AsyncSocket.h>
    4 #include <folly/io/async/EventHandler.h>
    5 #include <folly/io/IOExecutor.h>
    6 #include <iostream>
    7
    8 using namespace folly;
    9 using namespace std;
    10
    11 class ConnectionHandler : public EventHandler {
    12 public:
    13 explicit ConnectionHandler(AsyncSocket& socket) : socket_(socket) {}
    14
    15 void readDataAvailable(AsyncSocket& socket) noexcept override {
    16 // 处理socket可读事件
    17 IOBufQueue buf;
    18 socket.recv(buf);
    19 // 在这里处理接收到的数据,例如解析HTTP请求
    20 cout << "Received data: " << buf.front()->coalesce() << endl;
    21
    22 // 假设处理完请求后需要发送响应
    23 string response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!";
    24 IOBufQueue responseBuf;
    25 responseBuf.append(IOBuf::copyBuffer(response));
    26 socket.write(nullptr, responseBuf.move());
    27 }
    28
    29 void writeBufferAvailable(AsyncSocket& socket) noexcept override {
    30 // 处理socket可写事件,通常在发送数据时使用
    31 }
    32
    33 void connectionClosed(AsyncSocket& socket) noexcept override {
    34 cout << "Connection closed." << endl;
    35 delete this; // 注意:在实际应用中需要更完善的生命周期管理
    36 }
    37
    38 void connectSuccess(AsyncSocket& socket) noexcept override {}
    39 void connectError(AsyncSocket& socket, const AsyncSocketException& ex) noexcept override {}
    40 void readEOF(AsyncSocket& socket) noexcept override {}
    41 void writeSuccess(AsyncSocket& socket) noexcept override {}
    42 void writeError(AsyncSocket& socket, const AsyncSocketException& ex) noexcept override {}
    43
    44 private:
    45 AsyncSocket& socket_;
    46 };
    47
    48
    49 int main() {
    50 auto ioExecutor = IOExecutor::create(); // 创建 IOExecutor
    51 AsyncServerSocket serverSocket(ioExecutor); // 使用 IOExecutor 的 AsyncServerSocket
    52
    53 serverSocket.bind(SocketAddress::fromPort(8080));
    54 serverSocket.listen(1024);
    55 serverSocket.startAccepting();
    56
    57 serverSocket.getEventBase()->loopForever(); // 启动事件循环
    58
    59 return 0;
    60 }

    代码解释:

    IOExecutor::create(): 创建了一个 IOExecutor 实例。IOExecutor 负责管理 I/O 事件的监听和调度。
    AsyncServerSocket serverSocket(ioExecutor): 创建 AsyncServerSocket 时,将 IOExecutor 传递给它。这意味着 AsyncServerSocket 的 I/O 事件处理将由 IOExecutor 负责调度。
    ConnectionHandler: 一个简单的事件处理器,用于处理新连接上的 socket 事件,例如 readDataAvailable(socket 可读事件)。当 socket 上有数据可读时,readDataAvailable 方法会被 IOExecutor 调度执行。
    serverSocket.getEventBase()->loopForever(): 启动事件循环,IOExecutor 在事件循环中监听 I/O 事件,并在事件就绪时调度相应的事件处理器。

    在这个例子中,IOExecutor 负责监听 socket 的可读事件,当有数据到达时,IOExecutor 会调度 ConnectionHandler::readDataAvailable 方法来处理接收到的数据。这样,服务器就可以在等待 I/O 操作完成的同时,继续处理其他连接的事件,从而实现高并发。

    9.1.2 使用 CPUThreadPoolExecutor 处理业务逻辑 (Using CPUThreadPoolExecutor to Handle Business Logic)

    网络服务器除了处理网络 I/O 外,还需要执行业务逻辑,例如处理用户请求、访问数据库、计算结果等。这些业务逻辑通常是 CPU 密集型的,需要消耗大量的 CPU 资源。如果将业务逻辑处理也放在 IOExecutor 中执行,可能会阻塞 I/O 事件的处理,降低服务器的整体性能。

    CPUThreadPoolExecutor 专门用于处理 CPU 密集型任务。它维护一个线程池,并将提交的任务分配给线程池中的线程执行。这样可以将 CPU 密集型任务从 I/O 线程中分离出来,避免阻塞 I/O 操作,提高服务器的响应速度和吞吐量。

    以下代码示例展示了如何将业务逻辑处理提交到 CPUThreadPoolExecutor 中执行:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <folly/executors/CPUThreadPoolExecutor.h>
    3 #include <folly/io/async/AsyncServerSocket.h>
    4 #include <folly/io/async/AsyncSocket.h>
    5 #include <folly/io/async/EventHandler.h>
    6 #include <folly/io/IOExecutor.h>
    7 #include <iostream>
    8 #include <string>
    9
    10 using namespace folly;
    11 using namespace std;
    12
    13 class ConnectionHandler : public EventHandler {
    14 public:
    15 ConnectionHandler(AsyncSocket& socket, CPUThreadPoolExecutor& cpuExecutor)
    16 : socket_(socket), cpuExecutor_(cpuExecutor) {}
    17
    18 void readDataAvailable(AsyncSocket& socket) noexcept override {
    19 IOBufQueue buf;
    20 socket.recv(buf);
    21 string requestData = buf.front()->coalesce().str();
    22
    23 // 将业务逻辑处理提交到 CPUThreadPoolExecutor
    24 cpuExecutor_.add([this, requestData]() {
    25 string response = processRequest(requestData); // 模拟业务逻辑处理
    26 IOBufQueue responseBuf;
    27 responseBuf.append(IOBuf::copyBuffer(response));
    28
    29 // 注意:需要在 IOExecutor 线程中发送响应
    30 ioExecutor_->add([this, responseBuf = std::move(responseBuf)]() mutable {
    31 socket_.write(nullptr, responseBuf.move());
    32 });
    33 });
    34 }
    35
    36 string processRequest(const string& request) {
    37 // 模拟 CPU 密集型业务逻辑处理
    38 cout << "Processing request: " << request << " in CPU thread." << endl;
    39 // 实际业务逻辑...
    40 return "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!";
    41 }
    42
    43
    44 void writeBufferAvailable(AsyncSocket& socket) noexcept override {}
    45 void connectionClosed(AsyncSocket& socket) noexcept override {
    46 cout << "Connection closed." << endl;
    47 delete this;
    48 }
    49 void connectSuccess(AsyncSocket& socket) noexcept override {}
    50 void connectError(AsyncSocket& socket, const AsyncSocketException& ex) noexcept override {}
    51 void readEOF(AsyncSocket& socket) noexcept override {}
    52 void writeSuccess(AsyncSocket& socket) noexcept override {}
    53 void writeError(AsyncSocket& socket, const AsyncSocketException& ex) noexcept override {}
    54
    55
    56 private:
    57 AsyncSocket& socket_;
    58 CPUThreadPoolExecutor& cpuExecutor_;
    59 IOExecutor* ioExecutor_ = IOExecutor::get(); // 获取全局 IOExecutor
    60 };
    61
    62
    63 int main() {
    64 auto ioExecutor = IOExecutor::create();
    65 CPUThreadPoolExecutor cpuExecutor(4); // 创建 CPUThreadPoolExecutor,例如 4 个线程
    66 AsyncServerSocket serverSocket(ioExecutor);
    67
    68 serverSocket.bind(SocketAddress::fromPort(8080));
    69 serverSocket.listen(1024);
    70 serverSocket.startAccepting();
    71
    72 serverSocket.setNewConnectionHandler([&cpuExecutor](AsyncSocket& socket) {
    73 return new ConnectionHandler(socket, cpuExecutor); // 传递 CPUThreadPoolExecutor
    74 });
    75
    76
    77 serverSocket.getEventBase()->loopForever();
    78
    79 return 0;
    80 }

    代码解释:

    CPUThreadPoolExecutor cpuExecutor(4): 创建了一个包含 4 个线程的 CPUThreadPoolExecutor 实例。线程池的大小可以根据服务器的 CPU 核心数和业务负载进行调整。
    cpuExecutor_.add([this, requestData]() { ... });: 在 ConnectionHandler::readDataAvailable 方法中,当接收到客户端请求数据后,将业务逻辑处理 processRequest(requestData) 提交到 cpuExecutor_ 中执行。
    ioExecutor_->add([this, responseBuf = std::move(responseBuf)]() mutable { ... });: 在业务逻辑处理完成后,需要发送响应数据。由于 socket 的写操作需要在 IOExecutor 线程中执行,因此使用 ioExecutor_->add() 将发送响应的任务提交到 IOExecutor 中执行。IOExecutor::get() 用于获取全局的 IOExecutor 实例。

    通过将网络 I/O 处理和 CPU 密集型业务逻辑处理分别放在 IOExecutorCPUThreadPoolExecutor 中执行,可以充分利用系统资源,提高网络服务器的并发性能和响应速度。IOExecutor 负责高效地处理 I/O 事件,CPUThreadPoolExecutor 负责并行地执行 CPU 密集型任务,两者协同工作,构建出高性能的网络服务器。

    9.2 案例二:实现高效的数据处理流水线 (Case Study 2: Implementing an Efficient Data Processing Pipeline)

    数据处理流水线 (Data Processing Pipeline) 是一种常见的数据处理模式,它将复杂的数据处理任务分解为一系列独立的阶段 (Stage),每个阶段负责完成特定的数据处理操作。例如,一个典型的日志处理流水线可能包括:

    数据采集阶段 (Data Ingestion Stage):从不同的数据源(例如,文件、网络、消息队列)读取原始日志数据。
    数据解析阶段 (Data Parsing Stage):解析原始日志数据,提取出结构化的字段。
    数据转换阶段 (Data Transformation Stage):对解析后的数据进行转换、清洗、过滤等操作。
    数据存储阶段 (Data Storage Stage):将处理后的数据存储到数据库、文件系统或其他存储介质中。

    使用 Executor 模式可以有效地构建高效的数据处理流水线。每个阶段可以使用一个或多个 Executor 来并行处理数据,提高整体的处理吞吐量和效率。

    9.2.1 使用 Executor 构建数据处理阶段 (Using Executor to Build Data Processing Stages)

    在数据处理流水线中,每个阶段都可以抽象为一个任务队列和一个 Executor。任务队列用于存储待处理的数据,Executor 负责从任务队列中取出数据并执行相应的处理操作。

    例如,对于数据解析阶段,可以使用一个 ThreadPoolExecutor 来并行解析日志数据。数据采集阶段将采集到的原始日志数据放入解析阶段的任务队列中,ThreadPoolExecutor 中的线程从任务队列中取出数据并进行解析。解析后的数据可以放入下一个阶段(例如,数据转换阶段)的任务队列中,以此类推。

    以下代码示例展示了如何使用 Executor 构建一个简单的数据处理流水线的框架:

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/CPUThreadPoolExecutor.h>
    2 #include <folly/executors/InlineExecutor.h>
    3 #include <folly/futures/Future.h>
    4 #include <folly/synchronization/BlockingQueue.h>
    5 #include <iostream>
    6 #include <vector>
    7 #include <string>
    8
    9 using namespace folly;
    10 using namespace std;
    11
    12 // 任务队列
    13 template <typename T>
    14 using TaskQueue = BlockingQueue<T>;
    15
    16 // 数据处理阶段基类
    17 template <typename Input, typename Output>
    18 class DataProcessingStage {
    19 public:
    20 DataProcessingStage(Executor& executor) : executor_(executor) {}
    21
    22 // 提交任务到当前阶段
    23 Future<Output> submitTask(Input input) {
    24 auto promise = make_shared<Promise<Output>>();
    25 executor_.add([this, input, promise]() {
    26 try {
    27 Output output = processData(input);
    28 promise->setValue(std::move(output));
    29 } catch (const std::exception& ex) {
    30 promise->setException(ex);
    31 }
    32 });
    33 return promise->getFuture();
    34 }
    35
    36 protected:
    37 virtual Output processData(Input input) = 0; // 纯虚函数,子类实现
    38
    39 private:
    40 Executor& executor_;
    41 };
    42
    43 // 数据解析阶段
    44 class ParsingStage : public DataProcessingStage<string, pair<string, int>> { // 输入:原始日志字符串,输出:<日志类型, 时间戳>
    45 public:
    46 ParsingStage(Executor& executor) : DataProcessingStage<string, pair<string, int>>(executor) {}
    47
    48 protected:
    49 pair<string, int> processData(string rawLog) override {
    50 // 模拟日志解析逻辑
    51 cout << "Parsing log: " << rawLog << " in thread " << this_thread::get_id() << endl;
    52 // 实际解析逻辑...
    53 return { "TypeA", 1678886400 }; // 示例数据
    54 }
    55 };
    56
    57 // 数据转换阶段
    58 class TransformationStage : public DataProcessingStage<pair<string, int>, tuple<string, int, string>> { // 输入:<日志类型, 时间戳>,输出:<日志类型, 时间戳, 转换后的数据>
    59 public:
    60 TransformationStage(Executor& executor) : DataProcessingStage<pair<string, int>, tuple<string, int, string>>(executor) {}
    61
    62 protected:
    63 tuple<string, int, string> processData(pair<string, int> parsedData) override {
    64 // 模拟数据转换逻辑
    65 cout << "Transforming data: <" << parsedData.first << ", " << parsedData.second << "> in thread " << this_thread::get_id() << endl;
    66 // 实际转换逻辑...
    67 return { parsedData.first, parsedData.second, "TransformedData" }; // 示例数据
    68 }
    69 };
    70
    71 // 数据存储阶段 (使用 InlineExecutor 模拟同步存储)
    72 class StorageStage : public DataProcessingStage<tuple<string, int, string>, void> { // 输入:<日志类型, 时间戳, 转换后的数据>,输出:void
    73 public:
    74 StorageStage(Executor& executor) : DataProcessingStage<tuple<string, int, string>, void>(executor) {}
    75
    76 protected:
    77 void processData(tuple<string, int, string> transformedData) override {
    78 // 模拟数据存储逻辑
    79 cout << "Storing data: <" << get<0>(transformedData) << ", " << get<1>(transformedData) << ", " << get<2>(transformedData) << "> in thread " << this_thread::get_id() << endl;
    80 // 实际存储逻辑...
    81 }
    82 };
    83
    84
    85 int main() {
    86 CPUThreadPoolExecutor parsingExecutor(4); // 4 个线程用于解析
    87 CPUThreadPoolExecutor transformationExecutor(2); // 2 个线程用于转换
    88 InlineExecutor storageExecutor; // InlineExecutor 用于同步存储,简化示例
    89
    90 ParsingStage parsingStage(parsingExecutor);
    91 TransformationStage transformationStage(transformationExecutor);
    92 StorageStage storageStage(storageExecutor);
    93
    94 vector<string> rawLogs = { "log1", "log2", "log3", "log4", "log5", "log6" };
    95
    96 vector<Future<void>> storageFutures;
    97 for (const auto& rawLog : rawLogs) {
    98 auto storageFuture = parsingStage.submitTask(rawLog)
    99 .then(&transformationExecutor, [&](pair<string, int> parsedData) { // then() 默认使用前一个 Future 的 Executor,这里显式指定 transformationExecutor
    100 return transformationStage.submitTask(parsedData);
    101 })
    102 .then(&storageExecutor, [&](tuple<string, int, string> transformedData) { // 显式指定 storageExecutor
    103 return storageStage.submitTask(transformedData);
    104 });
    105 storageFutures.push_back(std::move(storageFuture));
    106 }
    107
    108 // 等待所有存储任务完成
    109 collectAll(storageFutures).get();
    110
    111 cout << "Data processing pipeline finished." << endl;
    112
    113 return 0;
    114 }

    代码解释:

    DataProcessingStage 基类: 定义了数据处理阶段的通用接口,包括 submitTask() 方法用于提交任务,和纯虚函数 processData() 留给子类实现具体的处理逻辑。
    ParsingStageTransformationStageStorageStage: 继承自 DataProcessingStage,分别实现了数据解析、转换和存储阶段的逻辑。每个阶段在 processData() 方法中模拟了相应的处理操作。
    CPUThreadPoolExecutor parsingExecutor(4)CPUThreadPoolExecutor transformationExecutor(2)InlineExecutor storageExecutor: 为每个阶段创建了不同的 Executor。解析和转换阶段使用 CPUThreadPoolExecutor 并行处理,存储阶段为了简化示例使用了 InlineExecutor 同步执行。在实际应用中,存储阶段也可能使用 ThreadPoolExecutorIOExecutor,取决于存储操作的特性。
    parsingStage.submitTask(rawLog).then(...): 使用 Future::then() 方法将流水线的各个阶段连接起来。then() 方法允许在当前 Future 完成后,将结果传递给下一个阶段的 Executor 执行。通过链式调用 then(),构建了完整的数据处理流水线。
    collectAll(storageFutures).get(): 等待所有流水线任务完成。

    9.2.2 Executor 的选择与流水线性能优化 (Executor Selection and Pipeline Performance Optimization)

    在构建数据处理流水线时,Executor 的选择对流水线的性能至关重要。不同的阶段可能需要不同类型的 Executor 来达到最佳性能。

    CPU 密集型阶段: 例如,数据解析、数据转换、复杂的计算等阶段,通常选择 CPUThreadPoolExecutor。线程池的大小应该根据 CPU 核心数和阶段的计算复杂度进行调整。过小的线程池可能无法充分利用 CPU 资源,过大的线程池可能会导致线程切换开销增加。

    I/O 密集型阶段: 例如,数据采集(从网络或磁盘读取数据)、数据存储(写入数据库或文件系统)等阶段,如果 I/O 操作是阻塞的,可以使用 ThreadPoolExecutorIOExecutor。如果 I/O 操作是非阻塞的,并且使用了事件驱动机制,则 IOExecutor 是更合适的选择。IOExecutor 可以高效地处理大量的并发 I/O 操作,避免线程阻塞。

    轻量级或同步阶段: 对于一些轻量级的处理阶段,或者需要同步执行的阶段(例如,某些数据验证或聚合操作),可以使用 InlineExecutorInlineExecutor 在当前线程中同步执行任务,避免了线程切换的开销。

    流水线性能优化策略:

    阶段并行化: 尽可能地将流水线的各个阶段并行化,使用 ThreadPoolExecutorIOExecutor 来提高每个阶段的处理能力。
    任务队列调优: 合理设置任务队列的大小,避免队列过长导致内存占用过高,或队列过短导致任务积压。可以使用有界队列 (Bounded Queue) 来限制队列的大小,并根据实际情况调整队列的容量。
    Executor 参数调优: 根据每个阶段的特性,调整 Executor 的参数,例如 ThreadPoolExecutor 的线程池大小、IOExecutor 的事件循环配置等。
    背压 (Backpressure) 控制: 在流水线中引入背压机制,防止上游阶段产生数据的速度超过下游阶段的处理能力,导致系统过载。可以使用流量控制、限速、熔断等技术来实现背压控制。
    监控与调优: 对流水线的性能进行监控,例如,监控每个阶段的任务处理时间、队列长度、资源利用率等。根据监控数据,识别性能瓶颈,并进行针对性的调优。可以使用 Folly 提供的 PerfCounter 等工具进行性能监控。

    通过合理选择 Executor 类型,并结合流水线性能优化策略,可以构建出高效、稳定、可扩展的数据处理流水线,满足各种复杂的数据处理需求。

    END_OF_CHAPTER

    10. chapter 10: Folly Executor API 全面解析 (Comprehensive API Analysis of Folly Executor)

    10.1 folly::Executor 类 API 详解 (Detailed API Explanation of folly::Executor Class)

    folly::Executor 是 Folly 库中所有具体 Executor 实现的抽象基类,它定义了执行任务的基本接口。理解 folly::Executor 的 API 是深入学习和使用 Folly Executor 框架的基础。本节将详细解析 folly::Executor 类的核心 API,帮助读者全面掌握其功能和用法。

    10.1.1 folly::Executor 接口概述 (Overview of folly::Executor Interface)

    folly::Executor 接口非常简洁,主要包含以下几个核心方法,它们共同构成了任务提交和执行的基础框架:

    execute(Func func) noexcept -> void
    schedule(Func func, Duration delay) -> ScheduledExecutor::ScheduledFuture
    getKeepAliveToken() -> std::shared_ptr<KeepAlive<Executor>>
    ~Executor() (virtual destructor)

    这些方法定义了 Executor 的基本行为,任何具体的 Executor 实现类都必须遵循这些接口约定。

    10.1.2 execute(Func func) noexcept -> void 方法详解 (Detailed Explanation of execute() Method)

    execute() 方法是 folly::Executor 接口中最核心的方法,用于提交一个待执行的任务。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual void execute(Func func) noexcept = 0;

    参数
    Func func:一个可调用对象(Callable Object),代表要执行的任务。Func 可以是函数指针、函数对象、Lambda 表达式等。folly::ExecutorFunc 的具体类型没有严格限制,只要它能够被调用即可。
    返回值
    voidexecute() 方法没有返回值。任务的执行结果通常通过 Future/Promise 机制来获取,这将在后续章节中详细介绍。
    异常
    noexceptexecute() 方法声明为 noexcept,意味着它保证不抛出任何异常。如果任务提交过程中发生错误,Executor 的实现需要自行处理,例如记录日志或采取其他容错措施,而不是抛出异常。
    功能描述
    execute() 方法的主要作用是将传入的可调用对象 func 提交给 Executor 执行。具体的执行方式和时机由 Executor 的具体实现决定。例如,ThreadPoolExecutor 会将任务放入线程池的任务队列中,等待线程池中的线程来执行;InlineExecutor 则会在调用 execute() 方法的线程中直接同步执行任务。
    execute() 方法是异步操作的入口。调用 execute() 方法后,通常情况下,提交任务的线程不会阻塞等待任务执行完成,而是立即返回。任务会在 Executor 管理的线程中异步执行。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/Executor.h>
    2 #include <iostream>
    3
    4 class MyExecutor : public folly::Executor {
    5 public:
    6 void execute(folly::Func func) noexcept override {
    7 std::cout << "Executing task in MyExecutor" << std::endl;
    8 func(); // 直接同步执行任务
    9 }
    10 };
    11
    12 int main() {
    13 MyExecutor executor;
    14 executor.execute([]{
    15 std::cout << "Hello from task!" << std::endl;
    16 });
    17 std::cout << "Task submitted." << std::endl;
    18 return 0;
    19 }

    代码解释
    ⚝ 上述代码示例展示了一个简单的自定义 Executor 实现 MyExecutor,它继承自 folly::Executor 并重写了 execute() 方法。
    MyExecutorexecute() 方法非常简单,它首先输出一条信息表示任务正在 MyExecutor 中执行,然后直接调用传入的任务函数 func(),实现同步执行。
    main() 函数中创建了 MyExecutor 实例,并使用 Lambda 表达式定义了一个简单的任务,然后通过 executor.execute() 提交任务。
    ⚝ 运行这段代码,可以看到输出结果中 "Executing task in MyExecutor" 和 "Hello from task!" 都在 "Task submitted." 之前,说明任务是在 execute() 方法调用期间同步执行的。

    10.1.3 schedule(Func func, Duration delay) -> ScheduledExecutor::ScheduledFuture 方法详解 (Detailed Explanation of schedule() Method)

    schedule() 方法用于提交一个延迟执行的任务。并非所有的 Executor 实现都支持延迟执行,只有实现了 ScheduledExecutor 接口的 Executor 才支持 schedule() 方法。folly::Executor 本身不直接提供 schedule() 的默认实现,而是通过 ScheduledExecutor 子接口来扩展延迟执行的功能。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual folly::ScheduledExecutor::ScheduledFuture schedule(
    2 folly::Func func,
    3 std::chrono::milliseconds delay) = 0;

    参数
    Func func:一个可调用对象,代表要延迟执行的任务。
    std::chrono::milliseconds delay:延迟时间,单位为毫秒。表示任务需要在 delay 时间后才能开始执行。
    返回值
    folly::ScheduledExecutor::ScheduledFuture:一个 ScheduledFuture 对象,用于表示延迟任务的未来结果和状态。通过 ScheduledFuture,可以取消任务的执行。
    异常
    ⚝ 具体的异常类型取决于 Executor 的实现。但通常情况下,schedule() 方法本身不应该抛出异常,除非参数无效或 Executor 内部状态异常。
    功能描述
    schedule() 方法允许提交一个任务,并指定该任务在延迟一段时间后才开始执行。这在需要定时任务或延迟任务处理的场景中非常有用。
    schedule() 方法返回一个 ScheduledFuture 对象,通过该对象可以对已提交的延迟任务进行管理,例如取消任务的执行。
    ⚝ 并非所有 Executor 都支持 schedule() 方法。只有实现了 ScheduledExecutor 接口的 Executor,例如 ScheduledThreadPoolExecutor,才提供 schedule() 的有效实现。对于不支持 schedule() 的 Executor,调用此方法可能会抛出异常或返回错误。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/executors/ScheduledExecutor.h>
    3 #include <iostream>
    4 #include <chrono>
    5 #include <thread>
    6
    7 int main() {
    8 // 创建一个支持延迟执行的线程池 Executor
    9 auto executor = std::make_shared<folly::ThreadPoolExecutor>(4);
    10 folly::ScheduledExecutor* scheduledExecutor =
    11 dynamic_cast<folly::ScheduledExecutor*>(executor.get());
    12
    13 if (scheduledExecutor) {
    14 std::cout << "Submitting delayed task..." << std::endl;
    15 auto future = scheduledExecutor->schedule(
    16 []{
    17 std::cout << "Delayed task executed after 2 seconds!" << std::endl;
    18 },
    19 std::chrono::milliseconds(2000) // 延迟 2 秒
    20 );
    21
    22 std::cout << "Task submitted. Waiting for 3 seconds..." << std::endl;
    23 std::this_thread::sleep_for(std::chrono::seconds(3)); // 主线程等待 3 秒
    24 std::cout << "Done waiting." << std::endl;
    25 } else {
    26 std::cerr << "Executor does not support schedule()" << std::endl;
    27 }
    28
    29 return 0;
    30 }

    代码解释
    ⚝ 上述代码示例使用了 ThreadPoolExecutor,并将其转换为 ScheduledExecutor* 指针,以使用 schedule() 方法。
    schedule() 方法提交了一个 Lambda 表达式任务,延迟 2 秒后执行。任务内容是输出 "Delayed task executed after 2 seconds!"。
    ⚝ 主线程休眠 3 秒,确保延迟任务有足够的时间执行。
    ⚝ 运行代码,可以看到 "Delayed task executed after 2 seconds!" 在 "Done waiting." 之前输出,验证了延迟任务的执行。

    10.1.4 getKeepAliveToken() -> std::shared_ptr<KeepAlive<Executor>> 方法详解 (Detailed Explanation of getKeepAliveToken() Method)

    getKeepAliveToken() 方法用于获取一个 KeepAlive token,用于管理 Executor 的生命周期。KeepAlive 机制是 Folly 中用于控制对象生命周期的一种技术,尤其适用于管理线程池等资源型对象的生命周期。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual std::shared_ptr<folly::KeepAlive<folly::Executor>> getKeepAliveToken() = 0;

    返回值
    std::shared_ptr<folly::KeepAlive<folly::Executor>>:一个指向 KeepAlive<Executor> 对象的 std::shared_ptr
    异常
    getKeepAliveToken() 方法通常不抛出异常。
    功能描述
    getKeepAliveToken() 方法返回一个 KeepAlive token。持有这个 token 可以防止 Executor 对象被过早销毁。
    KeepAlive 机制基于引用计数。当 Executor 对象创建时,其内部会维护一个 KeepAlive 计数器。每次调用 getKeepAliveToken() 方法,计数器会增加。当 KeepAlive token 对象被销毁时(例如 shared_ptr 析构),计数器会减少。只有当计数器降为零时,Executor 对象才会被真正销毁。
    KeepAlive 机制常用于确保 Executor 在任务执行期间保持存活。例如,在异步操作链中,如果 Executor 的生命周期与某个局部作用域绑定,可能会导致 Executor 在任务完成前就被销毁,从而引发错误。通过 KeepAlive token,可以延长 Executor 的生命周期,直到所有相关的异步操作完成。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/executors/KeepAlive.h>
    3 #include <iostream>
    4 #include <memory>
    5
    6 void submitTask(std::shared_ptr<folly::Executor> executor) {
    7 // 获取 KeepAlive token,延长 executor 的生命周期
    8 auto keepAliveToken = executor->getKeepAliveToken();
    9
    10 executor->execute([keepAliveToken]{ // Lambda 捕获 keepAliveToken
    11 std::cout << "Task executing..." << std::endl;
    12 std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时任务
    13 std::cout << "Task finished." << std::endl;
    14 // keepAliveToken 在 Lambda 结束时自动销毁,计数器减 1
    15 });
    16 std::cout << "Task submitted." << std::endl;
    17 // executor 在 submitTask 函数结束时,shared_ptr 引用计数减 1,但由于 keepAliveToken 的存在,Executor 不会被立即销毁
    18 }
    19
    20 int main() {
    21 auto executor = std::make_shared<folly::ThreadPoolExecutor>(4);
    22 submitTask(executor);
    23 std::cout << "submitTask function finished." << std::endl;
    24 std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待任务执行完成
    25 std::cout << "Program exiting." << std::endl;
    26 return 0;
    27 // main 函数结束时,executor 的 shared_ptr 引用计数减 1,如果 KeepAlive 计数器也为 0,则 Executor 会被销毁
    28 }

    代码解释
    submitTask() 函数接受一个 Executorshared_ptr,并在函数内部获取 KeepAlive token。
    ⚝ 提交的任务 Lambda 表达式捕获了 keepAliveToken。这意味着在任务执行期间,keepAliveToken 对象会一直存活,从而保证 Executor 对象不会被过早销毁。
    ⚝ 即使 submitTask() 函数执行完毕,executorshared_ptr 引用计数减少,但由于 keepAliveToken 仍然存在,Executor 对象仍然保持存活。
    ⚝ 任务执行完成后,Lambda 表达式结束,keepAliveToken 对象被销毁,KeepAlive 计数器减 1。当所有 KeepAlive token 都被销毁,且没有其他 shared_ptr 指向 Executor 时,Executor 才会被真正销毁。

    10.1.5 ~Executor() 析构函数详解 (Detailed Explanation of ~Executor() Destructor)

    ~Executor()folly::Executor 类的虚析构函数,用于在 Executor 对象生命周期结束时执行清理操作。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 virtual ~Executor() = default;

    功能描述
    ~Executor() 是一个虚析构函数,这意味着当通过基类指针删除派生类对象时,会调用派生类的析构函数,确保正确地释放派生类对象所占用的资源。
    = default 表示使用默认的析构函数实现。对于 folly::Executor 基类,默认析构函数通常不需要执行额外的清理操作。具体的资源清理工作通常在具体的 Executor 实现类(例如 ThreadPoolExecutor)的析构函数中完成。
    Executor 的析构函数会在 Executor 对象不再被使用时自动调用。这通常发生在 Executor 对象的 shared_ptr 引用计数降为零时。

    注意事项
    ⚝ 由于 ~Executor() 是虚函数,因此自定义 Executor 实现类时,应该重写析构函数,并在其中释放 Executor 占用的资源,例如线程池中的线程、任务队列等。
    ⚝ 在多线程环境中,需要确保析构函数的线程安全性,避免在析构过程中发生竞态条件或死锁。通常需要在析构函数中等待所有正在执行的任务完成,并安全地释放资源。

    总结

    folly::Executor 接口定义了并发编程中任务执行的基本抽象。execute() 方法用于提交任务,schedule() 方法用于提交延迟任务,getKeepAliveToken() 用于管理 Executor 生命周期,~Executor() 析构函数用于资源清理。理解这些核心 API 是使用 Folly Executor 框架的关键。在后续章节中,我们将深入探讨各种具体的 Executor 实现类,并学习如何根据不同的应用场景选择合适的 Executor。


    10.2 folly::ThreadPoolExecutor 类 API 详解 (Detailed API Explanation of folly::ThreadPoolExecutor Class)

    folly::ThreadPoolExecutor 是 Folly 库中最常用和功能强大的 Executor 实现之一,它基于线程池技术,能够高效地管理和调度多线程任务。本节将深入解析 folly::ThreadPoolExecutor 类的 API,帮助读者掌握其配置、使用和高级特性。

    10.2.1 ThreadPoolExecutor 类概述 (Overview of ThreadPoolExecutor Class)

    ThreadPoolExecutor 实现了 folly::Executor 接口,并提供了丰富的配置选项和管理功能,使其能够适应各种并发场景的需求。其核心特性包括:

    线程池管理:自动创建、管理和回收线程,减少线程创建和销毁的开销。
    任务队列:使用高效的任务队列来存储待执行的任务,支持多种队列类型和调度策略。
    线程池大小动态调整:可以根据系统负载和任务需求动态调整线程池的大小,提高资源利用率。
    丰富的配置选项:提供多种构造函数和配置参数,允许用户自定义线程池的行为。
    性能监控:集成 Folly PerfCounter 性能计数器,方便进行性能监控和调优。

    10.2.2 ThreadPoolExecutor 构造函数详解 (Detailed Explanation of ThreadPoolExecutor Constructors)

    ThreadPoolExecutor 提供了多个构造函数,允许用户根据不同的需求创建线程池。

    基本构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ThreadPoolExecutor(size_t numThreads, std::shared_ptr<BlockingQueue<Func>> queue = std::make_shared<LinkedBlockingQueue<Func>>());

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的初始线程数量。
    ▮▮▮▮⚝ std::shared_ptr<BlockingQueue<Func>> queue:任务队列,用于存储待执行的任务。默认为 LinkedBlockingQueue<Func>,即无界链表阻塞队列。
    功能
    ▮▮▮▮⚝ 创建一个固定大小线程池,初始线程数量为 numThreads
    ▮▮▮▮⚝ 使用指定的任务队列 queue,默认为无界队列。

    带线程工厂的构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ThreadPoolExecutor(size_t numThreads, std::shared_ptr<ThreadFactory> threadFactory, std::shared_ptr<BlockingQueue<Func>> queue = std::make_shared<LinkedBlockingQueue<Func>>());

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的初始线程数量。
    ▮▮▮▮⚝ std::shared_ptr<ThreadFactory> threadFactory:线程工厂,用于创建线程。用户可以自定义线程工厂,例如设置线程名称、优先级等。
    ▮▮▮▮⚝ std::shared_ptr<BlockingQueue<Func>> queue:任务队列,默认为无界队列。
    功能
    ▮▮▮▮⚝ 与基本构造函数类似,但允许用户自定义线程工厂,更灵活地控制线程的创建过程。

    带线程池选项的构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ThreadPoolExecutor(size_t numThreads, ThreadPoolExecutor::Options options);

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的初始线程数量。
    ▮▮▮▮⚝ ThreadPoolExecutor::Options options:线程池选项对象,用于配置线程池的各种参数,例如任务队列、线程工厂、拒绝策略等。
    功能
    ▮▮▮▮⚝ 通过 Options 对象,可以更全面地配置线程池的各种行为,例如设置任务队列类型、拒绝策略、线程空闲超时时间等。

    10.2.3 ThreadPoolExecutor::Options 详解 (Detailed Explanation of ThreadPoolExecutor::Options)

    ThreadPoolExecutor::Options 是一个结构体,用于配置 ThreadPoolExecutor 的各种选项。通过 Options,可以精细地控制线程池的行为。

    常用选项
    blockingQueueFactory:任务队列工厂,用于创建任务队列。可以自定义任务队列类型,例如 LifoBlockingQueue(LIFO 队列)、PriorityBlockingQueue(优先级队列)等。
    threadFactory:线程工厂,用于创建线程。
    maxQueueSize:任务队列的最大容量。当任务队列达到最大容量时,新的任务提交可能会被拒绝,具体行为取决于拒绝策略。默认为无界队列。
    rejectionPolicy:拒绝策略,当任务队列已满且线程池已饱和时,用于处理新提交的任务。常见的拒绝策略包括 AbortPolicy(抛出异常)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最旧的任务)、CallerRunsPolicy(在提交任务的线程中执行任务)等。
    idleThreadTimeout:空闲线程超时时间。当线程空闲时间超过此值时,线程池可能会回收空闲线程,减少资源消耗。
    enableTaskProfiling:是否启用任务性能分析。启用后,可以收集任务的执行时间等性能数据。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <folly/executors/queue/LifoBlockingQueue.h>
    3 #include <folly/executors/thread_factory/NamedThreadFactory.h>
    4 #include <folly/executors/rejectionpolicy/AbortPolicy.h>
    5 #include <iostream>
    6
    7 int main() {
    8 folly::ThreadPoolExecutor::Options options;
    9 options.blockingQueueFactory = [] { return std::make_shared<folly::LifoBlockingQueue<folly::Func>>(); }; // 使用 LIFO 队列
    10 options.threadFactory = std::make_shared<folly::NamedThreadFactory>("MyThreadPool"); // 设置线程名称
    11 options.rejectionPolicy = std::make_shared<folly::AbortPolicy>(); // 使用 AbortPolicy 拒绝策略
    12 options.maxQueueSize = 100; // 设置最大队列长度为 100
    13 options.idleThreadTimeout = std::chrono::seconds(60); // 空闲线程超时时间为 60 秒
    14
    15 auto executor = std::make_shared<folly::ThreadPoolExecutor>(4, options);
    16
    17 for (int i = 0; i < 5; ++i) {
    18 executor->execute([i]{
    19 std::cout << "Task " << i << " executing in thread " << std::this_thread::get_id() << std::endl;
    20 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    21 });
    22 }
    23 std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待任务执行完成
    24 return 0;
    25 }

    代码解释
    ⚝ 上述代码示例展示了如何使用 ThreadPoolExecutor::Options 来配置线程池。
    ⚝ 设置了任务队列为 LifoBlockingQueue(后进先出队列),线程工厂为 NamedThreadFactory(设置线程名称),拒绝策略为 AbortPolicy(抛出异常),最大队列长度为 100,空闲线程超时时间为 60 秒。
    ⚝ 创建线程池后,提交了 5 个任务。

    10.2.4 setNumThreads(size_t numThreads) 方法详解 (Detailed Explanation of setNumThreads() Method)

    setNumThreads() 方法用于动态调整线程池的线程数量。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void setNumThreads(size_t numThreads);

    参数
    size_t numThreads:新的线程数量。
    功能描述
    setNumThreads() 方法可以动态地修改线程池的线程数量。
    ⚝ 如果新的线程数量 numThreads 大于当前的线程数量,线程池会创建新的线程。
    ⚝ 如果新的线程数量 numThreads 小于当前的线程数量,线程池会尝试回收多余的空闲线程。但正在执行任务的线程不会被立即中断,只有当线程空闲下来后才会被回收。
    ⚝ 动态调整线程池大小可以根据系统负载和任务需求灵活地调整资源分配,提高资源利用率和系统性能。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <iostream>
    3 #include <thread>
    4
    5 int main() {
    6 auto executor = std::make_shared<folly::ThreadPoolExecutor>(2); // 初始 2 个线程
    7 std::cout << "Initial thread pool size: 2" << std::endl;
    8
    9 for (int i = 0; i < 4; ++i) {
    10 executor->execute([i]{
    11 std::cout << "Task " << i << " executing in thread " << std::this_thread::get_id() << std::endl;
    12 std::this_thread::sleep_for(std::chrono::milliseconds(200));
    13 });
    14 }
    15 std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 等待一段时间
    16
    17 executor->setNumThreads(4); // 动态调整为 4 个线程
    18 std::cout << "Thread pool size adjusted to: 4" << std::endl;
    19
    20 for (int i = 4; i < 8; ++i) {
    21 executor->execute([i]{
    22 std::cout << "Task " << i << " executing in thread " << std::this_thread::get_id() << std::endl;
    23 std::this_thread::sleep_for(std::chrono::milliseconds(200));
    24 });
    25 }
    26 std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待任务执行完成
    27 return 0;
    28 }

    代码解释
    ⚝ 初始创建了一个包含 2 个线程的 ThreadPoolExecutor
    ⚝ 提交 4 个任务后,等待一段时间,然后调用 setNumThreads(4) 将线程池大小动态调整为 4。
    ⚝ 再次提交 4 个任务。通过观察任务执行的线程 ID 和执行时间,可以感受到线程池大小调整带来的影响。

    10.2.5 getStats() 方法详解 (Detailed Explanation of getStats() Method)

    getStats() 方法用于获取线程池的统计信息,例如已完成任务数、正在执行任务数、任务队列大小等。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ThreadPoolExecutor::Stats getStats() const;

    返回值
    ThreadPoolExecutor::Stats:一个结构体,包含线程池的统计信息。
    ThreadPoolExecutor::Stats 结构体
    size_t submittedTaskCount:已提交的任务总数。
    size_t completedTaskCount:已完成的任务数。
    size_t rejectedTaskCount:被拒绝的任务数。
    size_t activeTaskCount:正在执行的任务数。
    size_t taskQueueSize:任务队列当前大小。
    size_t threadCount:当前线程池中的线程数量。
    size_t idleThreadCount:空闲线程数量。
    std::chrono::milliseconds lifetime:线程池的运行时间。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/ThreadPoolExecutor.h>
    2 #include <iostream>
    3 #include <thread>
    4
    5 int main() {
    6 auto executor = std::make_shared<folly::ThreadPoolExecutor>(2);
    7
    8 for (int i = 0; i < 3; ++i) {
    9 executor->execute([i]{
    10 std::cout << "Task " << i << " executing" << std::endl;
    11 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    12 });
    13 }
    14
    15 std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 等待一段时间,让任务执行完成
    16
    17 folly::ThreadPoolExecutor::Stats stats = executor->getStats();
    18 std::cout << "ThreadPool Stats:" << std::endl;
    19 std::cout << " Submitted Tasks: " << stats.submittedTaskCount << std::endl;
    20 std::cout << " Completed Tasks: " << stats.completedTaskCount << std::endl;
    21 std::cout << " Active Tasks: " << stats.activeTaskCount << std::endl;
    22 std::cout << " Task Queue Size: " << stats.taskQueueSize << std::endl;
    23 std::cout << " Thread Count: " << stats.threadCount << std::endl;
    24 std::cout << " Idle Thread Count: " << stats.idleThreadCount << std::endl;
    25 return 0;
    26 }

    代码解释
    ⚝ 创建 ThreadPoolExecutor 并提交 3 个任务。
    ⚝ 等待一段时间后,调用 getStats() 获取线程池的统计信息。
    ⚝ 输出统计信息,包括已提交任务数、已完成任务数、正在执行任务数、任务队列大小、线程数量和空闲线程数量等。

    10.2.6 其他常用 API (Other Common APIs)

    getTaskQueueSize():获取任务队列当前大小。
    isShutdown():判断线程池是否已关闭。
    isTerminating():判断线程池是否正在终止。
    isTerminated():判断线程池是否已终止。
    shutdown():平滑关闭线程池,不再接受新任务,但会等待已提交的任务执行完成。
    shutdownNow():立即关闭线程池,尝试中断正在执行的任务,并丢弃队列中等待执行的任务。

    总结

    folly::ThreadPoolExecutor 提供了丰富的 API 和配置选项,使其成为构建高性能并发应用的首选 Executor 实现。通过灵活地配置线程池大小、任务队列、拒绝策略等参数,可以满足各种复杂并发场景的需求。掌握 ThreadPoolExecutor 的 API 是深入理解和应用 Folly Executor 框架的关键。


    10.3 folly::InlineExecutor 类 API 详解 (Detailed API Explanation of folly::InlineExecutor Class)

    folly::InlineExecutor 是一种特殊的 Executor 实现,它不创建新的线程,而是在调用 execute() 方法的线程中直接同步执行任务InlineExecutor 非常轻量级,适用于单元测试、串行化执行或在特定场景下需要同步执行任务的场合。本节将详细解析 folly::InlineExecutor 类的 API 和特性。

    10.3.1 InlineExecutor 类概述 (Overview of InlineExecutor Class)

    InlineExecutor 的核心特点是同步执行。当通过 InlineExecutorexecute() 方法提交任务时,任务会立即在当前线程中执行,execute() 方法会阻塞直到任务执行完成才返回。这与线程池 Executor 的异步执行方式截然不同。

    同步执行:任务在提交任务的线程中同步执行。
    无线程管理:不创建和管理任何线程,完全依赖调用线程。
    轻量级:实现简单,开销极小,适用于对性能要求极高的同步执行场景。
    适用场景
    ▮▮▮▮⚝ 单元测试:方便在单线程环境中测试并发代码的逻辑。
    ▮▮▮▮⚝ 串行化执行:确保任务按照提交顺序依次执行。
    ▮▮▮▮⚝ 特定同步场景:例如在某些回调函数或事件处理函数中需要同步执行后续任务。

    10.3.2 InlineExecutor 构造函数详解 (Detailed Explanation of InlineExecutor Constructors)

    InlineExecutor 类只有一个默认构造函数,非常简单。

    默认构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 InlineExecutor();

    参数:无
    功能:创建一个 InlineExecutor 实例。由于 InlineExecutor 没有需要配置的参数,因此只需要默认构造函数即可。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 int main() {
    5 folly::InlineExecutor executor; // 创建 InlineExecutor 实例
    6 executor.execute([]{
    7 std::cout << "Task executing in InlineExecutor in thread " << std::this_thread::get_id() << std::endl;
    8 });
    9 std::cout << "Task submitted and executed." << std::endl;
    10 return 0;
    11 }

    代码解释
    ⚝ 创建了一个 InlineExecutor 实例 executor
    ⚝ 通过 executor.execute() 提交一个 Lambda 表达式任务。
    ⚝ 由于 InlineExecutor 是同步执行的,因此 "Task executing..." 会在 "Task submitted and executed." 之前输出,表明任务在 execute() 方法调用期间同步执行完成。

    10.3.3 execute(Func func) noexcept -> void 方法详解 (Detailed Explanation of execute() Method)

    InlineExecutorexecute() 方法是其核心方法,用于同步执行任务。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void execute(Func func) noexcept override;

    参数
    ▮▮▮▮⚝ Func func:一个可调用对象,代表要执行的任务。
    返回值void
    异常noexcept
    功能描述
    ▮▮▮▮⚝ execute() 方法直接在当前线程中调用传入的任务函数 func(),实现同步执行。
    ▮▮▮▮⚝ execute() 方法会阻塞当前线程,直到 func() 执行完成才返回。
    ▮▮▮▮⚝ 由于是同步执行,因此任务的执行顺序与提交顺序完全一致,保证了串行化执行。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3 #include <chrono>
    4 #include <thread>
    5
    6 int main() {
    7 folly::InlineExecutor executor;
    8
    9 for (int i = 0; i < 3; ++i) {
    10 std::cout << "Submitting task " << i << std::endl;
    11 executor.execute([i]{
    12 std::cout << "Task " << i << " executing in thread " << std::this_thread::get_id() << std::endl;
    13 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    14 });
    15 std::cout << "Task " << i << " executed." << std::endl;
    16 }
    17 std::cout << "All tasks submitted and executed." << std::endl;
    18 return 0;
    19 }

    代码解释
    ⚝ 循环提交 3 个任务到 InlineExecutor
    ⚝ 每次提交任务前后都输出信息。
    ⚝ 由于是同步执行,可以看到输出顺序为 "Submitting task 0", "Task 0 executing...", "Task 0 executed.", "Submitting task 1", "Task 1 executing...", "Task 1 executed.", ...,任务是串行执行的,并且在 execute() 方法返回前就已完成。

    10.3.4 schedule(Func func, Duration delay) 方法 (The schedule() Method)

    InlineExecutor 不支持延迟执行,因此 schedule() 方法会抛出异常。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 folly::ScheduledExecutor::ScheduledFuture schedule(Func func, std::chrono::milliseconds delay) override;

    异常
    ▮▮▮▮⚝ 调用 schedule() 方法会抛出 std::runtime_error 异常,提示 "InlineExecutor doesn't support schedule()".

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 int main() {
    5 folly::InlineExecutor executor;
    6 try {
    7 executor.schedule([]{}, std::chrono::milliseconds(1000)); // 尝试 schedule 延迟任务
    8 } catch (const std::runtime_error& e) {
    9 std::cerr << "Exception caught: " << e.what() << std::endl; // 捕获异常
    10 }
    11 return 0;
    12 }

    代码解释
    ⚝ 尝试调用 InlineExecutorschedule() 方法提交延迟任务。
    ⚝ 由于 InlineExecutor 不支持 schedule(),会抛出 std::runtime_error 异常。
    try-catch 块捕获异常,并输出错误信息 "InlineExecutor doesn't support schedule()".

    10.3.5 getKeepAliveToken() 方法 (The getKeepAliveToken() Method)

    InlineExecutorgetKeepAliveToken() 方法返回一个 KeepAlive token,但由于 InlineExecutor 本身生命周期管理非常简单,KeepAlive token 在 InlineExecutor 中实际上没有实际作用

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<folly::KeepAlive<folly::Executor>> getKeepAliveToken() override;

    返回值
    ▮▮▮▮⚝ 返回一个 std::shared_ptr<folly::KeepAlive<folly::Executor>> 对象。
    功能描述
    ▮▮▮▮⚝ getKeepAliveToken() 方法在 InlineExecutor 中被重写,但其实现非常简单,通常只是返回一个默认的 KeepAlive token。由于 InlineExecutor 没有复杂的资源管理和生命周期问题,KeepAlive 机制对其没有实际意义。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/InlineExecutor.h>
    2 #include <iostream>
    3
    4 int main() {
    5 folly::InlineExecutor executor;
    6 auto token = executor.getKeepAliveToken(); // 获取 KeepAlive token
    7 std::cout << "Got KeepAlive token from InlineExecutor." << std::endl;
    8 return 0;
    9 }

    代码解释
    ⚝ 调用 InlineExecutorgetKeepAliveToken() 方法获取 KeepAlive token。
    ⚝ 输出信息 "Got KeepAlive token from InlineExecutor.",表明可以正常获取 token,但这个 token 在 InlineExecutor 中并没有实际的生命周期管理作用。

    10.3.6 ~InlineExecutor() 析构函数 (The ~InlineExecutor() Destructor)

    InlineExecutor 的析构函数也是默认实现,由于 InlineExecutor 没有需要管理的资源,因此析构函数不需要执行任何额外的清理操作。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ~InlineExecutor() override = default;

    功能描述
    ▮▮▮▮⚝ 默认析构函数,无需手动释放资源。

    总结

    folly::InlineExecutor 是一种简单而实用的 Executor 实现,适用于需要同步执行任务的场景。其 API 非常简洁,核心方法是 execute(),用于同步执行任务。InlineExecutor 不支持延迟执行,schedule() 方法会抛出异常。KeepAlive 机制在 InlineExecutor 中没有实际作用。理解 InlineExecutor 的同步执行特性和适用场景,可以帮助开发者在合适的场合选择使用 InlineExecutor,简化代码逻辑,提高执行效率。


    10.4 folly::IOExecutor 类 API 详解 (Detailed API Explanation of folly::IOExecutor Class)

    folly::IOExecutor 是 Folly 库中专为 I/O 密集型任务 设计的 Executor 实现。它基于 epoll (Linux) 或 kqueue (macOS) 等高效的 I/O 多路复用机制,能够在一个或少量线程中高效地处理大量的 I/O 事件,非常适合构建高性能的网络服务器和 I/O 密集型应用。本节将深入解析 folly::IOExecutor 类的 API 和特性。

    10.4.1 IOExecutor 类概述 (Overview of IOExecutor Class)

    IOExecutor 的核心优势在于其 事件驱动非阻塞 I/O 模型。它使用 I/O 多路复用技术监听多个文件描述符(例如 socket),当文件描述符可读或可写时,IOExecutor 会调度相应的任务来处理 I/O 事件。

    事件驱动:基于 I/O 事件触发任务执行,而非传统的线程阻塞等待。
    非阻塞 I/O:使用非阻塞 I/O 操作,避免线程在 I/O 等待时被阻塞,提高线程利用率。
    高效 I/O 多路复用:底层使用 epoll/kqueue 等高效的 I/O 多路复用机制,能够在一个或少量线程中管理大量的 I/O 连接。
    适用于 I/O 密集型任务:特别适合处理网络 I/O、文件 I/O 等 I/O 密集型任务,例如网络服务器、代理服务器、数据库客户端等。
    单线程或少量线程:通常使用单线程或少量线程即可达到很高的并发性能,减少线程切换开销。

    10.4.2 IOExecutor 构造函数详解 (Detailed Explanation of IOExecutor Constructors)

    IOExecutor 提供了多个构造函数,允许用户根据不同的需求创建实例。

    默认构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 IOExecutor();

    参数:无
    功能:创建一个 IOExecutor 实例,使用默认的配置。通常情况下,默认构造函数已经足够满足大部分需求。

    带事件循环的构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 explicit IOExecutor(EventBase* evb);

    参数
    ▮▮▮▮⚝ EventBase* evb:一个 folly::EventBase 对象指针。EventBase 是 Folly 库中事件循环的核心组件,IOExecutor 依赖 EventBase 来处理 I/O 事件。用户可以传入自定义的 EventBase 实例,例如在需要将 IOExecutor 集成到现有的事件循环系统中时。
    功能
    ▮▮▮▮⚝ 创建一个 IOExecutor 实例,并使用指定的 EventBase 对象 evb 来处理 I/O 事件。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <iostream>
    4
    5 int main() {
    6 folly::IOExecutor executor1; // 使用默认构造函数
    7 folly::EventBase eventBase;
    8 folly::IOExecutor executor2(&eventBase); // 使用带 EventBase 的构造函数
    9
    10 executor1.execute([]{
    11 std::cout << "Task executing in IOExecutor 1" << std::endl;
    12 });
    13 executor2.execute([]{
    14 std::cout << "Task executing in IOExecutor 2 with custom EventBase" << std::endl;
    15 });
    16
    17 eventBase.loop(); // 启动事件循环,处理 IOExecutor 2 的任务 (如果需要)
    18 return 0;
    19 }

    代码解释
    ⚝ 创建了两个 IOExecutor 实例:executor1 使用默认构造函数,executor2 使用带 EventBase 的构造函数,并传入了一个自定义的 EventBase 对象 eventBase
    ⚝ 分别向两个 IOExecutor 提交任务。
    ⚝ 对于 executor2,需要手动调用 eventBase.loop() 启动事件循环,才能处理 IOExecutor 提交的任务。对于默认构造函数创建的 IOExecutor,其内部会自动管理事件循环。

    10.4.3 execute(Func func) noexcept -> void 方法详解 (Detailed Explanation of execute() Method)

    IOExecutorexecute() 方法用于提交 I/O 相关的任务。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void execute(Func func) noexcept override;

    参数
    ▮▮▮▮⚝ Func func:一个可调用对象,代表要执行的 I/O 任务。
    返回值void
    异常noexcept
    功能描述
    ▮▮▮▮⚝ execute() 方法将任务 func 提交给 IOExecutor 的事件循环处理。
    ▮▮▮▮⚝ IOExecutor 会将任务注册到事件循环中,等待 I/O 事件就绪时调度执行。
    ▮▮▮▮⚝ 任务 func 应该是非阻塞的,避免在任务内部进行长时间的阻塞操作,以免影响事件循环的效率。如果需要执行阻塞 I/O 操作,应该将其放到单独的线程中执行,然后通过 IOExecutor 回调处理结果。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <folly/io/async/EventBase.h>
    3 #include <iostream>
    4 #include <fstream>
    5
    6 int main() {
    7 folly::IOExecutor executor;
    8 std::string filename = "example.txt";
    9
    10 executor.execute([filename]{
    11 std::ifstream inputFile(filename);
    12 if (inputFile.is_open()) {
    13 std::string line;
    14 while (std::getline(inputFile, line)) {
    15 std::cout << "Read line: " << line << std::endl;
    16 }
    17 inputFile.close();
    18 } else {
    19 std::cerr << "Failed to open file: " << filename << std::endl;
    20 }
    21 });
    22
    23 // 注意:IOExecutor 通常需要事件循环来驱动,但在这个简单的例子中,
    24 // Folly 内部可能做了简化处理,使得即使没有显式启动事件循环也能工作。
    25 // 在实际的网络编程中,通常需要配合 EventBase::loop() 使用。
    26
    27 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 等待任务执行完成
    28 return 0;
    29 }

    代码解释
    ⚝ 提交一个任务到 IOExecutor,任务内容是读取文件 "example.txt" 的内容并输出到控制台。
    ⚝ 这个例子中,文件 I/O 操作虽然是阻塞的,但在简单的示例中可能可以工作。在实际的网络编程中,应该避免在 IOExecutor 任务中执行阻塞 I/O 操作,而应该使用非阻塞 I/O 和事件驱动的方式

    10.4.4 schedule(Func func, Duration delay) 方法 (The schedule() Method)

    IOExecutor 支持延迟执行schedule() 方法可以将任务注册到事件循环中,延迟一段时间后执行。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 folly::ScheduledExecutor::ScheduledFuture schedule(Func func, std::chrono::milliseconds delay) override;

    参数
    ▮▮▮▮⚝ Func func:一个可调用对象,代表要延迟执行的任务。
    ▮▮▮▮⚝ std::chrono::milliseconds delay:延迟时间,单位为毫秒。
    返回值
    ▮▮▮▮⚝ folly::ScheduledExecutor::ScheduledFuture:一个 ScheduledFuture 对象,用于管理延迟任务。
    功能描述
    ▮▮▮▮⚝ schedule() 方法将延迟任务 func 注册到 IOExecutor 的事件循环中。
    ▮▮▮▮⚝ 事件循环会在延迟时间到达后,调度执行任务 func

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/IOExecutor.h>
    2 #include <iostream>
    3 #include <chrono>
    4 #include <thread>
    5
    6 int main() {
    7 folly::IOExecutor executor;
    8 std::cout << "Submitting delayed task to IOExecutor..." << std::endl;
    9 executor.schedule([]{
    10 std::cout << "Delayed task executed in IOExecutor after 1 second!" << std::endl;
    11 }, std::chrono::milliseconds(1000)); // 延迟 1 秒
    12
    13 std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待任务执行完成
    14 return 0;
    15 }

    代码解释
    ⚝ 使用 IOExecutorschedule() 方法提交一个延迟任务,延迟时间为 1 秒。
    ⚝ 主线程休眠 2 秒,确保延迟任务有足够的时间执行。
    ⚝ 运行代码,可以看到 "Delayed task executed in IOExecutor after 1 second!" 在主线程休眠结束后输出,验证了延迟任务的执行。

    10.4.5 getKeepAliveToken() 方法 (The getKeepAliveToken() Method)

    IOExecutorgetKeepAliveToken() 方法与 ThreadPoolExecutor 类似,用于获取 KeepAlive token,管理 IOExecutor 的生命周期。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<folly::KeepAlive<folly::Executor>> getKeepAliveToken() override;

    返回值
    ▮▮▮▮⚝ std::shared_ptr<folly::KeepAlive<folly::Executor>>:一个 KeepAlive token。
    功能描述
    ▮▮▮▮⚝ getKeepAliveToken() 方法返回一个 KeepAlive token,用于延长 IOExecutor 的生命周期,防止其被过早销毁。

    10.4.6 ~IOExecutor() 析构函数 (The ~IOExecutor() Destructor)

    IOExecutor 的析构函数负责清理 IOExecutor 占用的资源,例如停止事件循环、释放事件监听器等。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ~IOExecutor() override;

    功能描述
    ▮▮▮▮⚝ ~IOExecutor() 析构函数会停止 IOExecutor 内部的事件循环,并释放相关的资源。
    ▮▮▮▮⚝ 在 IOExecutor 对象不再使用时,其析构函数会被自动调用,完成资源清理工作。

    总结

    folly::IOExecutor 是构建高性能 I/O 密集型应用的利器。它基于事件驱动和非阻塞 I/O 模型,能够高效地处理大量的 I/O 事件。IOExecutor 的 API 与其他 Executor 类似,核心方法包括 execute()schedule()。在实际应用中,通常需要配合 folly::EventBase 和非阻塞 I/O 操作来发挥 IOExecutor 的最大性能。理解 IOExecutor 的事件驱动模型和非阻塞 I/O 特性,可以帮助开发者构建高效、可扩展的网络应用。


    10.5 folly::CPUThreadPoolExecutor 类 API 详解 (Detailed API Explanation of folly::CPUThreadPoolExecutor Class)

    folly::CPUThreadPoolExecutor 是 Folly 库中专门为 CPU 密集型任务 优化的线程池 Executor。与通用的 ThreadPoolExecutor 相比,CPUThreadPoolExecutor 在线程管理和调度策略上进行了一些调整,以更好地适应 CPU 密集型任务的特点,例如减少线程切换开销,提高 CPU 缓存命中率等。本节将详细解析 folly::CPUThreadPoolExecutor 类的 API 和特性。

    10.5.1 CPUThreadPoolExecutor 类概述 (Overview of CPUThreadPoolExecutor Class)

    CPUThreadPoolExecutor 的设计目标是最大化 CPU 密集型任务的执行效率。它通常会配置与 CPU 核心数相近的线程数量,并采用一些策略来减少线程切换和提高 CPU 缓存效率。

    针对 CPU 密集型任务优化:专门为 CPU 密集型计算任务设计,例如图像处理、科学计算、数据分析等。
    线程数量通常与 CPU 核心数相关:默认线程数量通常设置为 CPU 核心数或略多于核心数,以充分利用 CPU 资源,同时避免过多的线程切换开销。
    可能采用 NUMA 感知的线程调度:在 NUMA (Non-Uniform Memory Access) 架构的系统中,CPUThreadPoolExecutor 可能会采用 NUMA 感知的线程调度策略,尽量将线程和其访问的数据分配到同一个 NUMA 节点,提高内存访问效率。
    继承自 ThreadPoolExecutorCPUThreadPoolExecutor 实际上是 ThreadPoolExecutor 的一个子类,继承了 ThreadPoolExecutor 的大部分功能和 API,并在其基础上进行了一些优化。

    10.5.2 CPUThreadPoolExecutor 构造函数详解 (Detailed Explanation of CPUThreadPoolExecutor Constructors)

    CPUThreadPoolExecutor 提供了多个构造函数,与 ThreadPoolExecutor 的构造函数类似,但有一些针对 CPU 密集型任务的默认配置。

    默认构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 CPUThreadPoolExecutor(size_t numThreads);

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的线程数量。通常建议设置为 CPU 核心数或略多于核心数。
    功能
    ▮▮▮▮⚝ 创建一个 CPUThreadPoolExecutor 实例,线程数量为 numThreads
    ▮▮▮▮⚝ 使用默认的任务队列和线程工厂等配置。

    带线程工厂和队列的构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 CPUThreadPoolExecutor(size_t numThreads, std::shared_ptr<ThreadFactory> threadFactory, std::shared_ptr<BlockingQueue<Func>> queue = std::make_shared<LinkedBlockingQueue<Func>>());

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的线程数量。
    ▮▮▮▮⚝ std::shared_ptr<ThreadFactory> threadFactory:线程工厂。
    ▮▮▮▮⚝ std::shared_ptr<BlockingQueue<Func>> queue:任务队列,默认为 LinkedBlockingQueue<Func>
    功能
    ▮▮▮▮⚝ 与 ThreadPoolExecutor 类似,允许用户自定义线程工厂和任务队列。

    带线程池选项的构造函数

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 CPUThreadPoolExecutor(size_t numThreads, ThreadPoolExecutor::Options options);

    参数
    ▮▮▮▮⚝ size_t numThreads:线程池的线程数量。
    ▮▮▮▮⚝ ThreadPoolExecutor::Options options:线程池选项对象,用于配置线程池的各种参数。
    功能
    ▮▮▮▮⚝ 与 ThreadPoolExecutor 类似,通过 Options 对象可以全面配置线程池的行为。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/CPUThreadPoolExecutor.h>
    2 #include <folly/executors/thread_factory/NamedThreadFactory.h>
    3 #include <iostream>
    4
    5 int main() {
    6 size_t numCores = std::thread::hardware_concurrency(); // 获取 CPU 核心数
    7 folly::CPUThreadPoolExecutor executor1(numCores); // 使用默认构造函数,线程数与核心数相同
    8 folly::CPUThreadPoolExecutor executor2(numCores + 1, std::make_shared<folly::NamedThreadFactory>("CPUThreadPool")); // 自定义线程工厂
    9
    10 executor1.execute([]{
    11 std::cout << "Task executing in CPUThreadPoolExecutor 1" << std::endl;
    12 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟 CPU 密集型任务
    13 });
    14 executor2.execute([]{
    15 std::cout << "Task executing in CPUThreadPoolExecutor 2 with named threads" << std::endl;
    16 std::this_thread::sleep_for(std::chrono::milliseconds(100));
    17 });
    18
    19 std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 等待任务执行完成
    20 return 0;
    21 }

    代码解释
    ⚝ 获取 CPU 核心数 numCores
    ⚝ 创建两个 CPUThreadPoolExecutor 实例:executor1 使用默认构造函数,线程数设置为核心数;executor2 自定义了线程工厂,并设置线程数为核心数 + 1。
    ⚝ 分别向两个 CPUThreadPoolExecutor 提交任务,模拟 CPU 密集型计算任务。

    10.5.3 execute(Func func) noexcept -> void 方法详解 (Detailed Explanation of execute() Method)

    CPUThreadPoolExecutorexecute() 方法与 ThreadPoolExecutorexecute() 方法功能相同,用于提交任务。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 void execute(Func func) noexcept override;

    参数
    ▮▮▮▮⚝ Func func:一个可调用对象,代表要执行的 CPU 密集型任务。
    返回值void
    异常noexcept
    功能描述
    ▮▮▮▮⚝ execute() 方法将任务 func 提交给 CPUThreadPoolExecutor 的线程池执行。
    ▮▮▮▮⚝ CPUThreadPoolExecutor 会根据其内部的调度策略,将任务分配给线程池中的线程执行。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/CPUThreadPoolExecutor.h>
    2 #include <iostream>
    3 #include <chrono>
    4 #include <cmath>
    5
    6 int main() {
    7 size_t numCores = std::thread::hardware_concurrency();
    8 folly::CPUThreadPoolExecutor executor(numCores);
    9
    10 for (int i = 0; i < 4; ++i) {
    11 executor.execute([i]{
    12 std::cout << "Task " << i << " executing in CPUThreadPoolExecutor in thread " << std::this_thread::get_id() << std::endl;
    13 double result = 0;
    14 for (int j = 0; j < 1000000; ++j) { // 模拟 CPU 密集型计算
    15 result += std::sqrt(j);
    16 }
    17 std::cout << "Task " << i << " finished, result = " << result << std::endl;
    18 });
    19 }
    20
    21 std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待任务执行完成
    22 return 0;
    23 }

    代码解释
    ⚝ 创建 CPUThreadPoolExecutor,线程数与 CPU 核心数相同。
    ⚝ 提交 4 个任务,每个任务内部进行一个简单的 CPU 密集型计算(计算平方根累加)。
    ⚝ 通过观察任务执行的线程 ID 和执行时间,可以感受到 CPUThreadPoolExecutor 在处理 CPU 密集型任务时的效率。

    10.5.4 schedule(Func func, Duration delay) 方法 (The schedule() Method)

    CPUThreadPoolExecutor 支持延迟执行schedule() 方法与 ThreadPoolExecutorschedule() 方法功能相同。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 folly::ScheduledExecutor::ScheduledFuture schedule(Func func, std::chrono::milliseconds delay) override;

    参数
    ▮▮▮▮⚝ Func func:一个可调用对象,代表要延迟执行的任务。
    ▮▮▮▮⚝ std::chrono::milliseconds delay:延迟时间。
    返回值
    ▮▮▮▮⚝ folly::ScheduledExecutor::ScheduledFuture:一个 ScheduledFuture 对象。
    功能描述
    ▮▮▮▮⚝ schedule() 方法将延迟任务提交给 CPUThreadPoolExecutor,延迟一段时间后执行。

    代码示例

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 #include <folly/executors/CPUThreadPoolExecutor.h>
    2 #include <iostream>
    3 #include <chrono>
    4 #include <thread>
    5
    6 int main() {
    7 size_t numCores = std::thread::hardware_concurrency();
    8 folly::CPUThreadPoolExecutor executor(numCores);
    9
    10 std::cout << "Submitting delayed task to CPUThreadPoolExecutor..." << std::endl;
    11 executor.schedule([]{
    12 std::cout << "Delayed task executed in CPUThreadPoolExecutor after 1 second!" << std::endl;
    13 }, std::chrono::milliseconds(1000)); // 延迟 1 秒
    14
    15 std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待任务执行完成
    16 return 0;
    17 }

    代码解释
    ⚝ 使用 CPUThreadPoolExecutorschedule() 方法提交一个延迟任务,延迟时间为 1 秒。
    ⚝ 主线程休眠 2 秒,等待延迟任务执行完成。

    10.5.5 getKeepAliveToken() 方法 (The getKeepAliveToken() Method)

    CPUThreadPoolExecutorgetKeepAliveToken() 方法与 ThreadPoolExecutor 相同,用于获取 KeepAlive token,管理 CPUThreadPoolExecutor 的生命周期。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 std::shared_ptr<folly::KeepAlive<folly::Executor>> getKeepAliveToken() override;

    返回值
    ▮▮▮▮⚝ std::shared_ptr<folly::KeepAlive<folly::Executor>>:一个 KeepAlive token。
    功能描述
    ▮▮▮▮⚝ getKeepAliveToken() 方法返回一个 KeepAlive token,用于延长 CPUThreadPoolExecutor 的生命周期。

    10.5.6 ~CPUThreadPoolExecutor() 析构函数 (The ~CPUThreadPoolExecutor() Destructor)

    CPUThreadPoolExecutor 的析构函数负责清理线程池资源,与 ThreadPoolExecutor 的析构函数类似。

    方法签名

    1.双击鼠标左键复制此行;2.单击复制所有代码。
                                    
                                        
    1 ~CPUThreadPoolExecutor() override;

    功能描述
    ▮▮▮▮⚝ ~CPUThreadPoolExecutor() 析构函数会等待线程池中的任务执行完成,并释放线程池占用的资源。

    总结

    folly::CPUThreadPoolExecutor 是针对 CPU 密集型任务优化的线程池 Executor。它继承自 ThreadPoolExecutor,并针对 CPU 密集型任务的特点进行了优化,例如线程数量通常与 CPU 核心数相关,可能采用 NUMA 感知的调度策略等。CPUThreadPoolExecutor 的 API 与 ThreadPoolExecutor 基本一致,核心方法包括 execute()schedule()。在需要处理 CPU 密集型任务时,CPUThreadPoolExecutor 通常是比通用 ThreadPoolExecutor 更合适的选择。

    END_OF_CHAPTER

    11. chapter 11: 总结与展望 (Summary and Future Outlook)

    11.1 Folly Executor 的优势与局限性 (Advantages and Limitations of Folly Executor)

    在本书的结尾,我们对 folly::Executor 进行了全面的回顾和总结。folly::Executor 作为 Facebook Folly 库中重要的组件,为现代 C++ 并发编程提供了强大而灵活的工具。本节将深入探讨 folly::Executor 的优势与局限性,帮助读者更清晰地认识其适用场景和潜在的不足。

    11.1.1 folly::Executor 的主要优势 (Key Advantages of folly::Executor)

    强大的抽象能力 (Powerful Abstraction)folly::Executor 提供了一层高度抽象的接口,将任务提交与执行策略解耦。开发者无需关心任务如何在底层线程池或特定执行环境中运行,只需关注任务本身。这种抽象简化了并发编程的复杂性,提高了代码的可读性和可维护性。
    灵活的执行策略 (Flexible Execution Policies):Folly 提供了多种内置的 Executor 实现,如 ThreadPoolExecutor(线程池执行器)、InlineExecutor(内联执行器)、IOExecutor(I/O 执行器)和 CPUThreadPoolExecutor(CPU 线程池执行器)等。每种 Executor 都针对不同的应用场景进行了优化,开发者可以根据具体需求选择最合适的执行策略。此外,folly::Executor 还支持自定义 Executor 实现,满足更 विशिष्ट 的并发需求。
    高效的任务调度与管理 (Efficient Task Scheduling and Management)folly::Executor 框架内置了高效的任务队列和调度机制,能够有效地管理和调度大量的并发任务。例如,ThreadPoolExecutor 采用了优化的工作窃取(Work-Stealing)算法,最大限度地提高了线程的利用率和任务的吞吐量。
    与 Folly 库的深度集成 (Deep Integration with Folly Library)folly::Executor 与 Folly 库的其他组件(如 Future/PromiseEventBase 等)无缝集成,共同构建了一个强大的异步编程生态系统。这种集成使得开发者可以更方便地利用 Folly 库的各种功能,构建高性能、高可靠性的并发应用。
    良好的可扩展性与可定制性 (Good Scalability and Customizability)folly::Executor 的设计充分考虑了可扩展性和可定制性。开发者可以通过继承 folly::Executor 接口或组合现有的 Executor 实现,构建满足特定需求的自定义执行器。同时,folly::Executor 的各种配置选项也允许开发者根据实际负载动态调整执行器的行为。
    完善的监控与调优支持 (Comprehensive Monitoring and Tuning Support):Folly 提供了 PerfCounter 等工具,可以方便地监控 Executor 的性能指标,如任务队列长度、线程池大小、任务执行时间等。这些监控数据可以帮助开发者深入了解 Executor 的运行状况,并进行性能调优。

    11.1.2 folly::Executor 的局限性与挑战 (Limitations and Challenges of folly::Executor)

    学习曲线 (Learning Curve):虽然 folly::Executor 提供了相对简洁的 API,但要充分理解其背后的并发模型和各种 Executor 的特性,仍然需要一定的学习成本。对于并发编程初学者来说,可能需要花费一些时间来掌握 folly::Executor 的使用方法和最佳实践。
    依赖 Folly 库 (Dependency on Folly Library)folly::Executor 是 Folly 库的一部分,使用 folly::Executor 就意味着需要引入整个 Folly 库。对于一些小型项目或对依赖库有严格限制的项目,引入 Folly 库可能会显得过于重量级。
    C++ 语言的复杂性 (Complexity of C++ Language):C++ 语言本身的复杂性也给 folly::Executor 的使用带来了一定的挑战。例如,C++ 的内存管理、对象生命周期等问题在并发编程中尤为重要,开发者需要具备扎实的 C++ 基础才能有效地使用 folly::Executor 构建可靠的并发程序。
    错误处理的复杂性 (Complexity of Error Handling):并发编程中的错误处理往往比顺序编程更加复杂。在 folly::Executor 中,任务的异常可能会在不同的线程中抛出,如何有效地捕获和处理这些异常,保证程序的健壮性,是开发者需要认真考虑的问题。
    调试难度 (Debugging Difficulty):并发程序的调试通常比顺序程序更困难。由于线程的执行顺序和时序关系的不确定性,并发 Bug 往往难以复现和定位。使用 folly::Executor 构建的并发程序也不可避免地面临调试的挑战,需要开发者掌握一定的并发调试技巧和工具。
    资源管理的复杂性 (Complexity of Resource Management):不当的资源管理是并发编程中常见的问题。例如,线程池大小设置不合理、任务队列无限制增长等都可能导致系统资源耗尽,影响程序的性能和稳定性。使用 folly::Executor 时,需要仔细考虑资源管理策略,避免潜在的资源泄漏和性能瓶颈。

    11.2 未来并发编程技术的发展趋势 (Development Trends in Future Concurrent Programming Technologies)

    随着计算机硬件的不断发展和应用场景的日益复杂,并发编程技术也在持续演进。展望未来,我们可以预见以下几个重要的发展趋势:

    异步编程模型的普及 (Popularization of Asynchronous Programming Models):异步编程(Asynchronous Programming)模型,如基于 async/await 关键字的编程方式,将越来越受到欢迎。异步编程能够有效地提高程序的并发性和响应性,尤其在 I/O 密集型和事件驱动型应用中具有显著优势。folly::Future/Promise协程(Coroutines)等技术正是异步编程的重要组成部分,未来将在更多场景中得到应用。
    反应式编程范式的兴起 (Rise of Reactive Programming Paradigm):反应式编程(Reactive Programming)范式强调数据流和变化传播,能够优雅地处理异步事件和数据流。反应式编程模型非常适合构建高并发、低延迟的实时系统。例如,处理用户界面事件、实时数据分析、物联网(IoT)数据流等。未来,反应式编程将在更多领域发挥重要作用。
    Actor 模型和消息传递机制的复兴 (Revival of Actor Model and Message Passing Mechanism):Actor 模型(Actor Model)是一种基于消息传递的并发模型,它将并发单元(Actor)隔离起来,通过消息进行通信,避免了共享状态带来的并发问题。Actor 模型具有良好的可扩展性和容错性,在分布式系统和高并发应用中具有优势。随着分布式计算和微服务架构的兴起,Actor 模型和消息传递机制有望迎来新的发展机遇。
    硬件加速与异构计算 (Hardware Acceleration and Heterogeneous Computing):随着 CPU 核心数量的增加和 GPU、FPGA 等异构计算设备的普及,利用硬件加速来提升并发性能将成为重要的发展方向。例如,将计算密集型任务卸载到 GPU 上执行,利用 FPGA 实现硬件加速的并发算法等。未来的并发编程技术需要更好地利用这些硬件资源,实现更高的性能和效率。
    形式化验证与并发安全 (Formal Verification and Concurrency Safety):并发程序的正确性验证一直是并发编程领域的难题。随着形式化验证(Formal Verification)技术的发展,未来有望出现更多工具和方法,帮助开发者验证并发程序的正确性,减少并发 Bug 的发生。同时,编程语言和工具链也将提供更强的并发安全保障,例如,通过类型系统、静态分析等手段,在编译时或运行时检测并发错误。
    无锁数据结构与算法的优化 (Optimization of Lock-Free Data Structures and Algorithms):锁(Lock)是并发编程中常用的同步机制,但锁的使用可能会引入性能瓶颈和死锁等问题。无锁数据结构(Lock-Free Data Structures)和算法(Lock-Free Algorithms)通过原子操作等技术,在不使用锁的情况下实现并发安全的数据访问和操作。未来,无锁技术将在高性能并发编程中发挥越来越重要的作用。
    领域特定并发编程框架的涌现 (Emergence of Domain-Specific Concurrent Programming Frameworks):针对特定领域的并发编程需求,将会涌现出更多领域特定的并发编程框架。例如,针对游戏开发、金融交易、机器学习等领域的并发编程框架,这些框架将提供更高级别的抽象和更优化的并发策略,简化特定领域并发应用的开发。

    11.3 持续学习与深入探索 (Continuous Learning and In-depth Exploration)

    并发编程是一个充满挑战和机遇的领域。掌握 folly::Executor 只是并发编程学习旅程的开始。为了更好地应对未来的并发编程挑战,我们鼓励读者持续学习和深入探索:

    深入学习并发编程理论 (In-depth Study of Concurrent Programming Theory):扎实的理论基础是掌握并发编程技术的关键。建议读者深入学习并发模型(如共享内存模型、消息传递模型)、并发算法、并发数据结构、并发控制理论等。经典书籍如《Java Concurrency in Practice》、《并发的艺术》等都是很好的学习资源。
    实践与项目经验积累 (Practice and Project Experience Accumulation):理论学习固然重要,但实践才是检验真理的唯一标准。建议读者通过实际项目练习,将所学的并发编程知识应用到实际场景中。例如,可以尝试使用 folly::Executor 构建高性能的网络服务器、数据处理流水线、并发任务调度系统等。
    关注开源社区与技术发展动态 (Pay Attention to Open Source Community and Technology Development Trends):并发编程技术发展迅速,新的技术、工具和框架不断涌现。建议读者关注 Folly、Boost.Asio、libuv 等优秀的开源社区,了解最新的并发编程技术发展动态。同时,关注学术界和工业界的最新研究成果,掌握并发编程的前沿技术。
    参与并发编程相关的技术交流与讨论 (Participate in Technical Exchange and Discussion Related to Concurrent Programming):技术交流和讨论是学习和进步的重要途径。建议读者积极参与并发编程相关的技术社区、论坛、会议等,与其他开发者交流经验、分享心得、共同进步。
    持续探索 Folly 库的其他组件 (Continuously Explore Other Components of Folly Library)folly::Executor 只是 Folly 库众多强大组件之一。Folly 库还提供了 Future/PromiseEventBaseFBVectorStringPiece 等丰富的工具和库,可以极大地提升 C++ 开发效率和程序性能。建议读者持续探索 Folly 库的其他组件,充分利用 Folly 库的强大功能。

    并发编程之路漫漫,但充满乐趣与挑战。希望本书能够帮助读者迈出学习 folly::Executor 和并发编程的第一步。相信通过持续学习和实践,读者一定能够掌握并发编程的精髓,构建出更加高效、可靠、强大的并发应用。🚀

    END_OF_CHAPTER