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 源码

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_forspawn)提供类型检查,确保传入的 "调度器参数" 确实符合调度器接口。

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_forspawn 创建的异步任务),是框架默认调度器的基础。
2.5.4 default_threadpool_schedulerdefault_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_taskrun_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_forspawn 即可享受并行加速。

3. 总结

Async++ 的调度器机制是框架并发能力的核心,通过抽象调度接口和提供多种内置实现,实现了 "任务创建" 与 "任务执行" 的解耦:

  • 核心接口:schedule(task_run_handle) 定义了任务调度的契约;
  • 内置调度器:覆盖了立即执行、线程池、手动调度等场景;
  • 灵活性:支持自定义调度器,适配特殊硬件或业务需求。

理解调度器的工作原理,有助于正确选择调度策略,优化并行任务的执行效率。

相关推荐
掘根3 小时前
【Qt】网络编程
开发语言·qt
Never_Satisfied3 小时前
在JavaScript / HTML中,词内断行
开发语言·javascript·html
小猪佩奇TONY3 小时前
C++ 学习(3) ----设计模式
c++·学习·设计模式
码界筑梦坊3 小时前
276-基于Python的爱奇艺视频数据可视化分析系统
开发语言·python·信息可视化
bawangtianzun4 小时前
重链剖分 学习记录
数据结构·c++·学习·算法
头发掉光的程序员5 小时前
第九章 纹理贴图
c++·图形渲染·direct12
一念&9 小时前
每日一个C语言知识:C 数据类型
c语言·开发语言
进击中的小龙9 小时前
在vscode下的cmake项目里传参调试c++命令行程序
c++·vscode
迈火10 小时前
PuLID_ComfyUI:ComfyUI中的图像生成强化插件
开发语言·人工智能·python·深度学习·计算机视觉·stable diffusion·语音识别