深入解析:Async++ 源码分析11--schedule_fwd.h
一、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 源码
// 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  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().schedule(std::declval()))>
two& is_scheduler_helper(int);
template
one& is_scheduler_helper(...);
template
struct is_scheduler: public std::integral_constant(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_ptr;
// Helper function to schedule a task using a scheduler
template
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 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 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&& prerun_,
                                         std::function&& 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::type default_scheduler_type;
} // namespace detail
} // namespace async             
        这段代码是 Async++ 框架中调度器(Scheduler)机制的核心定义,位于 async 及其内部的 detail:: 命名空间,核心功能是抽象任务调度逻辑—— 定义了 “任务如何被分配到线程执行” 的接口和多种内置调度器实现,是框架并发任务执行的 “指挥中心”。以下是详细解析:
2.2. 核心概念:调度器(Scheduler)
在 Async++ 中,“调度器” 是一个抽象接口,其核心职责是接收任务(task_run_handle)并决定何时、在哪个线程上执行该任务(通过调用任务的 run() 方法)。框架通过调度器抽象,将 “任务创建” 与 “任务执行” 解耦,支持不同场景的调度策略(如线程池、立即执行、工作窃取等)。
2.3 调度器接口定义
代码通过 “鸭子类型”(Duck Typing)定义调度器接口 —— 任何类型只要实现了以下函数,就被视为 “调度器”:
// 调度器必须实现的核心接口:接收任务并安排执行
void schedule(async::task_run_handle t);
- 输入:
task_run_handle是任务的 “执行句柄”,封装了任务的核心执行逻辑(通过t.run()触发任务执行); - 行为:调度器需保证 
t.run()在未来某个时刻被调用(具体线程和时机由调度器决定)。 
2.4. 调度器类型检测:detail::is_scheduler
通过 SFINAE 机制,编译期判断一个类型是否为 “调度器”(即是否实现了 schedule 接口):
// 辅助函数:若 T 有 schedule 方法(参数为 task_run_handle),匹配此重载(返回 two&,大小 2)
template().schedule(std::declval()))>
two& is_scheduler_helper(int);
// 辅助函数:若 T 无符合要求的 schedule 方法,匹配此重载(返回 one&,大小 1)
template
one& is_scheduler_helper(...);
// 类型判断:通过 helper 函数返回值大小,确定 T 是否为调度器
template
struct is_scheduler: public std::integral_constant(0)) - 1> {};     
- 作用:为框架中的函数(如 
parallel_for、spawn)提供类型检查,确保传入的 “调度器参数” 确实符合调度器接口。 
2.5. 内置调度器实现
框架提供多种内置调度器,适配不同的执行场景,用户可根据需求选择。
2.5.1 inline_scheduler:立即在当前线程执行任务
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:在新线程中执行任务
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)” 线程池实现,高效利用多核资源:
class threadpool_scheduler {
    std::unique_ptr 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&& prerun_,
                                         std::function&& 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:默认调度器
// 内置线程池调度器(单例):线程数由环境变量 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 队列调度器
任务被加入队列,需手动调用方法触发执行,支持线程安全的任务添加和执行:
class fifo_scheduler {
    struct internal_data;
    std::unique_ptr 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
模板函数,用于通过调度器调度任务(内部实现,简化框架代码):
template
void schedule_task(Sched& sched, task_ptr t); 
- 作用:将 
task_ptr(任务智能指针)包装为task_run_handle,再调用调度器的schedule方法,完成任务提交。 
2.6.2 detail::wait_for_task
阻塞等待任务完成的底层函数:
LIBASYNC_EXPORT void wait_for_task(task_base* wait_task);
- 作用:当线程需要等待某个任务完成时(如 
task.get()),调用该函数阻塞当前线程,直到任务执行完毕; - 实现:可能通过条件变量等同步机制,让线程进入休眠状态,避免忙等。
 
2.6.3 detail::default_scheduler_type
默认调度器的类型别名(解决编译器对 decltype 的处理问题):
typedef std::remove_reference::type default_scheduler_type; 
2.7. 调度器的典型用法
示例 1:使用默认调度器(线程池)
#include 
#include 
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 立即执行
#include 
#include 
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:自定义线程池调度器
#include 
#include 
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 手动执行
#include 
#include 
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)定义了任务调度的契约; - 内置调度器:覆盖了立即执行、线程池、手动调度等场景;
 - 灵活性:支持自定义调度器,适配特殊硬件或业务需求。
 
理解调度器的工作原理,有助于正确选择调度策略,优化并行任务的执行效率。
                    
                
                
            
        
浙公网安备 33010602011771号