一、Async++ 代码目录结构
Async++ 项目的目录结构清晰,主要包含根目录下的配置文件、源代码目录、头文件目录以及示例代码目录,具体结构如下:
asyncplusplus/
├── .gitignore # Git 忽略文件配置
├── Async++Config.cmake.in # CMake 配置模板文件
├── CMakeLists.txt # CMake 构建脚本
├── LICENSE # 许可证文件(MIT 许可证)
├── README.md # 项目说明文档
├── examples/ # 示例代码目录
│ └── gtk_scheduler.cpp # GTK 调度器示例
├── src/ # 源代码目录
│ ├── fifo_queue.h # FIFO 队列实现
│ ├── internal.h # 内部头文件(包含类型定义、宏等)
│ ├── scheduler.cpp # 调度器实现
│ ├── singleton.h # 单例模式实现
│ ├── task_wait_event.h # 任务等待事件实现
│ ├── threadpool_scheduler.cpp # 线程池调度器实现
│ └── work_steal_queue.h # 工作窃取队列实现
└── include/ # 头文件目录
├── async++.h # 主头文件(对外提供统一接口)
└── async++/ # 子模块头文件目录
├── aligned_alloc.h
├── cancel.h
├── continuation_vector.h
├── parallel_for.h
├── parallel_invoke.h
├── parallel_reduce.h
├── partitioner.h # 分区器相关定义
├── range.h # 范围(迭代器对)相关定义
├── ref_count.h
├── scheduler.h # 调度器接口定义
├── scheduler_fwd.h
├── task.h # 任务类定义
├── task_base.h # 任务基类定义
├── traits.h
└── when_all_any.h
二、chedule_fwd.h源码分析
2.1 源码
cpp
// Copyright (c) 2015 Amanieu d'Antras
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef ASYNCXX_H_
# error "Do not include this header directly, include <async++.h> instead."
#endif
namespace async {
// Forward declarations
class task_run_handle;
class threadpool_scheduler;
// Scheduler interface:
// A scheduler is any type that implements this function:
// void schedule(async::task_run_handle t);
// This function should result in t.run() being called at some future point.
namespace detail {
// Detect whether an object is a scheduler
template<typename T, typename = decltype(std::declval<T>().schedule(std::declval<task_run_handle>()))>
two& is_scheduler_helper(int);
template<typename T>
one& is_scheduler_helper(...);
template<typename T>
struct is_scheduler: public std::integral_constant<bool, sizeof(is_scheduler_helper<T>(0)) - 1> {};
// Singleton scheduler classes
class thread_scheduler_impl {
public:
LIBASYNC_EXPORT static void schedule(task_run_handle t);
};
class inline_scheduler_impl {
public:
static void schedule(task_run_handle t);
};
// Reference counted pointer to task data
struct task_base;
typedef ref_count_ptr<task_base> task_ptr;
// Helper function to schedule a task using a scheduler
template<typename Sched>
void schedule_task(Sched& sched, task_ptr t);
// Wait for the given task to finish. This will call the wait handler currently
// active for this thread, which causes the thread to sleep by default.
LIBASYNC_EXPORT void wait_for_task(task_base* wait_task);
// Forward-declaration for data used by threadpool_scheduler
struct threadpool_data;
} // namespace detail
// Run a task in the current thread as soon as it is scheduled
inline detail::inline_scheduler_impl& inline_scheduler()
{
static detail::inline_scheduler_impl instance;
return instance;
}
// Run a task in a separate thread. Note that this scheduler does not wait for
// threads to finish at process exit. You must ensure that all threads finish
// before ending the process.
inline detail::thread_scheduler_impl& thread_scheduler()
{
static detail::thread_scheduler_impl instance;
return instance;
}
// Built-in thread pool scheduler with a size that is configurable from the
// LIBASYNC_NUM_THREADS environment variable. If that variable does not exist
// then the number of CPUs in the system is used instead.
LIBASYNC_EXPORT threadpool_scheduler& default_threadpool_scheduler();
// Default scheduler that is used when one isn't specified. This defaults to
// default_threadpool_scheduler(), but can be overriden by defining
// LIBASYNC_CUSTOM_DEFAULT_SCHEDULER before including async++.h. Keep in mind
// that in that case async::default_scheduler should be declared before
// including async++.h.
#ifndef LIBASYNC_CUSTOM_DEFAULT_SCHEDULER
inline threadpool_scheduler& default_scheduler()
{
return default_threadpool_scheduler();
}
#endif
// Scheduler that holds a list of tasks which can then be explicitly executed
// by a thread. Both adding and running tasks are thread-safe operations.
class fifo_scheduler {
struct internal_data;
std::unique_ptr<internal_data> impl;
public:
LIBASYNC_EXPORT fifo_scheduler();
LIBASYNC_EXPORT ~fifo_scheduler();
// Add a task to the queue
LIBASYNC_EXPORT void schedule(task_run_handle t);
// Try running one task from the queue. Returns false if the queue was empty.
LIBASYNC_EXPORT bool try_run_one_task();
// Run all tasks in the queue
LIBASYNC_EXPORT void run_all_tasks();
};
// Scheduler that runs tasks in a work-stealing thread pool of the given size.
// Note that destroying the thread pool before all tasks have completed may
// result in some tasks not being executed.
class threadpool_scheduler {
std::unique_ptr<detail::threadpool_data> impl;
public:
LIBASYNC_EXPORT threadpool_scheduler(threadpool_scheduler&& other);
// Create a thread pool with the given number of threads
LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads);
// Create a thread pool with the given number of threads. Call `prerun`
// function before execution loop and `postrun` after.
LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads,
std::function<void()>&& prerun_,
std::function<void()>&& postrun_);
// Destroy the thread pool, tasks that haven't been started are dropped
LIBASYNC_EXPORT ~threadpool_scheduler();
// Schedule a task to be run in the thread pool
LIBASYNC_EXPORT void schedule(task_run_handle t);
};
namespace detail {
// Work-around for Intel compiler handling decltype poorly in function returns
typedef std::remove_reference<decltype(::async::default_scheduler())>::type default_scheduler_type;
} // namespace detail
} // namespace async
这段代码是 Async++ 框架中调度器(Scheduler)机制 的核心定义,位于 async
及其内部的 detail::
命名空间,核心功能是抽象任务调度逻辑------ 定义了 "任务如何被分配到线程执行" 的接口和多种内置调度器实现,是框架并发任务执行的 "指挥中心"。以下是详细解析:
2.2. 核心概念:调度器(Scheduler)
在 Async++ 中,"调度器" 是一个抽象接口 ,其核心职责是接收任务(task_run_handle
)并决定何时、在哪个线程上执行该任务(通过调用任务的 run()
方法)。框架通过调度器抽象,将 "任务创建" 与 "任务执行" 解耦,支持不同场景的调度策略(如线程池、立即执行、工作窃取等)。
2.3 调度器接口定义
代码通过 "鸭子类型"(Duck Typing)定义调度器接口 ------ 任何类型只要实现了以下函数,就被视为 "调度器":
cpp
// 调度器必须实现的核心接口:接收任务并安排执行
void schedule(async::task_run_handle t);
- 输入:
task_run_handle
是任务的 "执行句柄",封装了任务的核心执行逻辑(通过t.run()
触发任务执行); - 行为:调度器需保证
t.run()
在未来某个时刻被调用(具体线程和时机由调度器决定)。
2.4. 调度器类型检测:detail::is_scheduler
通过 SFINAE 机制,编译期判断一个类型是否为 "调度器"(即是否实现了 schedule
接口):
cpp
// 辅助函数:若 T 有 schedule 方法(参数为 task_run_handle),匹配此重载(返回 two&,大小 2)
template<typename T, typename = decltype(std::declval<T>().schedule(std::declval<task_run_handle>()))>
two& is_scheduler_helper(int);
// 辅助函数:若 T 无符合要求的 schedule 方法,匹配此重载(返回 one&,大小 1)
template<typename T>
one& is_scheduler_helper(...);
// 类型判断:通过 helper 函数返回值大小,确定 T 是否为调度器
template<typename T>
struct is_scheduler: public std::integral_constant<bool, sizeof(is_scheduler_helper<T>(0)) - 1> {};
- 作用:为框架中的函数(如
parallel_for
、spawn
)提供类型检查,确保传入的 "调度器参数" 确实符合调度器接口。
2.5. 内置调度器实现
框架提供多种内置调度器,适配不同的执行场景,用户可根据需求选择。
2.5.1 inline_scheduler
:立即在当前线程执行任务
cpp
class inline_scheduler_impl {
public:
static void schedule(task_run_handle t); // 核心调度接口
};
// 获取单例实例
inline detail::inline_scheduler_impl& inline_scheduler() {
static detail::inline_scheduler_impl instance;
return instance;
}
- 调度逻辑 :
schedule
方法直接调用t.run()
,任务在调用schedule
的当前线程立即执行(同步执行,无异步调度); - 适用场景:调试场景(任务执行顺序确定)、不希望任务被异步调度的场景(如性能敏感的短任务)。
2.5.2 thread_scheduler
:在新线程中执行任务
cpp
class thread_scheduler_impl {
public:
LIBASYNC_EXPORT static void schedule(task_run_handle t); // 核心调度接口
};
// 获取单例实例
inline detail::thread_scheduler_impl& thread_scheduler() {
static detail::thread_scheduler_impl instance;
return instance;
}
- 调度逻辑 :
schedule
方法创建一个新线程,在新线程中调用t.run()
,任务在独立线程中异步执行; - 注意事项:不保证线程回收(进程退出时不会等待线程完成),需用户手动确保任务执行完毕;
- 适用场景:需要完全隔离的异步任务(如长时间运行的后台任务)。
2.5.3 threadpool_scheduler
:工作窃取线程池调度器
这是框架最核心的调度器,基于 "工作窃取(Work-Stealing)" 线程池实现,高效利用多核资源:
cpp
class threadpool_scheduler {
std::unique_ptr<detail::threadpool_data> impl; // 内部数据(线程池状态)
public:
// 构造函数:
// 1. 移动构造(线程池不可拷贝,仅可移动)
LIBASYNC_EXPORT threadpool_scheduler(threadpool_scheduler&& other);
// 2. 创建指定线程数的线程池
LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads);
// 3. 创建线程池,并指定线程启动前/退出后的回调(如初始化/清理资源)
LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads,
std::function<void()>&& prerun_,
std::function<void()>&& postrun_);
// 析构函数:销毁线程池(未启动的任务会被丢弃)
LIBASYNC_EXPORT ~threadpool_scheduler();
// 核心调度接口:将任务加入线程池的任务队列,由空闲线程执行
LIBASYNC_EXPORT void schedule(task_run_handle t);
};
- 核心特性 :
- 线程池内的线程会从自己的任务队列取任务执行,若队列空则 "窃取" 其他线程的任务(负载均衡);
- 线程数可自定义(通常设为 CPU 核心数,充分利用多核);
- 适用场景 :绝大多数并行任务(如
parallel_for
、spawn
创建的异步任务),是框架默认调度器的基础。
2.5.4 default_threadpool_scheduler
与 default_scheduler
:默认调度器
cpp
// 内置线程池调度器(单例):线程数由环境变量 LIBASYNC_NUM_THREADS 决定,默认使用 CPU 核心数
LIBASYNC_EXPORT threadpool_scheduler& default_threadpool_scheduler();
// 默认调度器:默认指向 default_threadpool_scheduler,可通过宏自定义
#ifndef LIBASYNC_CUSTOM_DEFAULT_SCHEDULER
inline threadpool_scheduler& default_scheduler() {
return default_threadpool_scheduler();
}
#endif
- 作用 :框架中所有未指定调度器的函数(如
parallel_for
无调度器参数版本)都会使用default_scheduler()
,简化用户调用; - 自定义 :通过定义
LIBASYNC_CUSTOM_DEFAULT_SCHEDULER
宏,可替换默认调度器(需在包含async++.h
前声明)。
2.5.5 fifo_scheduler
:手动触发的 FIFO 队列调度器
任务被加入队列,需手动调用方法触发执行,支持线程安全的任务添加和执行:
cpp
class fifo_scheduler {
struct internal_data;
std::unique_ptr<internal_data> impl; // 内部 FIFO 队列和同步机制
public:
LIBASYNC_EXPORT fifo_scheduler();
LIBASYNC_EXPORT ~fifo_scheduler();
// 1. 将任务加入队列(线程安全)
LIBASYNC_EXPORT void schedule(task_run_handle t);
// 2. 尝试执行队列中的一个任务(返回 false 表示队列为空,线程安全)
LIBASYNC_EXPORT bool try_run_one_task();
// 3. 执行队列中所有任务(线程安全)
LIBASYNC_EXPORT void run_all_tasks();
};
- 核心特性 :任务按加入顺序(FIFO)执行,但执行时机由用户手动控制(调用
try_run_one_task
或run_all_tasks
); - 适用场景:需要手动控制任务执行时机的场景(如测试、模拟调度、协程框架集成)。
2.6. 辅助函数与类型
2.6.1 detail::schedule_task
模板函数,用于通过调度器调度任务(内部实现,简化框架代码):
cpp
template<typename Sched>
void schedule_task(Sched& sched, task_ptr t);
- 作用:将
task_ptr
(任务智能指针)包装为task_run_handle
,再调用调度器的schedule
方法,完成任务提交。
2.6.2 detail::wait_for_task
阻塞等待任务完成的底层函数:
cpp
LIBASYNC_EXPORT void wait_for_task(task_base* wait_task);
- 作用:当线程需要等待某个任务完成时(如
task.get()
),调用该函数阻塞当前线程,直到任务执行完毕; - 实现:可能通过条件变量等同步机制,让线程进入休眠状态,避免忙等。
2.6.3 detail::default_scheduler_type
默认调度器的类型别名(解决编译器对 decltype
的处理问题):
cpp
typedef std::remove_reference<decltype(::async::default_scheduler())>::type default_scheduler_type;
2.7. 调度器的典型用法
示例 1:使用默认调度器(线程池)
cpp
#include <async++.h>
#include <iostream>
int main() {
// 默认调度器(threadpool_scheduler),自动使用 CPU 核心数线程
async::spawn([]() {
std::cout << "Task executed in thread pool\n";
});
// 等待任务完成(默认调度器的线程池会处理任务)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 0;
}
示例 2:使用 inline_scheduler
立即执行
cpp
#include <async++.h>
#include <iostream>
int main() {
std::cout << "Before task\n";
// 使用 inline_scheduler,任务在当前线程立即执行
async::spawn(async::inline_scheduler(), []() {
std::cout << "Task executed inline\n";
});
std::cout << "After task\n";
// 输出顺序:Before → Task → After(同步执行)
return 0;
}
示例 3:自定义线程池调度器
cpp
#include <async++.h>
#include <iostream>
int main() {
// 创建包含 2 个线程的线程池
async::threadpool_scheduler pool(2);
// 提交任务到自定义线程池
async::parallel_for(pool, async::irange(0, 4), [](int i) {
std::cout << "Task " << i << " (Thread: " << std::this_thread::get_id() << ")\n";
});
// 线程池会用 2 个线程并行执行 4 个任务
return 0;
}
示例 4:使用 fifo_scheduler
手动执行
cpp
#include <async++.h>
#include <iostream>
int main() {
async::fifo_scheduler fifo;
// 向队列添加任务(不会立即执行)
fifo.schedule(async::task_run_handle([]() {
std::cout << "Task 1 executed\n";
}));
fifo.schedule(async::task_run_handle([]() {
std::cout << "Task 2 executed\n";
}));
std::cout << "Before running tasks\n";
// 手动执行所有任务(按添加顺序执行)
fifo.run_all_tasks();
std::cout << "After running tasks\n";
return 0;
}
2.8. 核心设计亮点
(1)接口抽象,策略灵活
通过 "schedule
方法" 定义调度器接口,框架无需关心具体调度逻辑,用户可实现自定义调度器(如基于操作系统线程池、GPU 线程等)。
(2)多种调度器适配场景
- 立即执行(
inline_scheduler
):适合调试和短任务; - 线程池(
threadpool_scheduler
):适合大多数并行任务,高效利用多核; - 手动调度(
fifo_scheduler
):适合需要精确控制执行时机的场景。
(3)工作窃取提升效率
threadpool_scheduler
采用工作窃取算法,避免线程空闲(某线程任务少则窃取其他线程任务),提升 CPU 利用率。
(4)默认调度器简化使用
default_scheduler
隐藏了调度器的细节,大多数用户无需关心底层实现,直接调用 parallel_for
、spawn
即可享受并行加速。
3. 总结
Async++ 的调度器机制是框架并发能力的核心,通过抽象调度接口和提供多种内置实现,实现了 "任务创建" 与 "任务执行" 的解耦:
- 核心接口:
schedule(task_run_handle)
定义了任务调度的契约; - 内置调度器:覆盖了立即执行、线程池、手动调度等场景;
- 灵活性:支持自定义调度器,适配特殊硬件或业务需求。
理解调度器的工作原理,有助于正确选择调度策略,优化并行任务的执行效率。