Executor 的 _spawn 方法和构造函数共同负责创建和初始化线程池中的工作线程,并为任务调度和执行提供支持。以下是对代码的详细解析:
1. 构造函数
// Constructor
inline Executor::Executor(size_t N, std::shared_ptr<WorkerInterface> wix) :
_workers (N),
_notifier (N),
_buffers (N),
_worker_interface(std::move(wix)) {
if(N == 0) {
TF_THROW("executor must define at least one worker");
}
_spawn(N);
// initialize the default observer if requested
if(has_env(TF_ENABLE_PROFILER)) {
TFProfManager::get()._manage(make_observer<TFProfObserver>());
}
}
功能:
- 初始化线程池相关资源:
- _workers(N):初始化大小为 N 的工作线程容器。
- _notifier(N):初始化大小为 N 的通知器容器,用于线程间的同步。
- _buffers(N):初始化大小为 N 的缓冲区容器,可能用于存储任务队列或其他数据。
- _worker_interface(std::move(wix)):使用用户提供的 WorkerInterface 对象。
- 检查线程池大小:
if(N == 0) {
TF_THROW("executor must define at least one worker");
}
- 确保线程池大小至少为 1,否则抛出异常。
- 调用 _spawn 方法:
_spawn(N);
- 创建并启动 N 个工作线程。
- 性能分析器初始化:
if(has_env(TF_ENABLE_PROFILER)) {
TFProfManager::get()._manage(make_observer<TFProfObserver>());
}
- 如果启用了性能分析功能,则注册性能分析器以监控任务流的执行。
2. _spawn 方法
// Procedure: _spawn
inline void Executor::_spawn(size_t N) {
for(size_t id=0; id<N; ++id) {
_workers[id]._id = id;
_workers[id]._vtm = id;
_workers[id]._executor = this;
_workers[id]._waiter = &_notifier._waiters[id];
_workers[id]._thread = std::thread([&, &w=_workers[id]] () {
pt::this_worker = &w;
// initialize the random engine and seed for work-stealing loop
w._rdgen.seed(static_cast<std::default_random_engine::result_type>(
std::hash<std::thread::id>()(std::this_thread::get_id()))
);
// before entering the work-stealing loop, call the scheduler prologue
if(_worker_interface) {
_worker_interface->scheduler_prologue(w);
}
Node* t = nullptr;
std::exception_ptr ptr = nullptr;
// must use 1 as condition instead of !done because
// the previous worker may stop while the following workers
// are still preparing for entering the scheduling loop
try {
// worker loop
while(1) {
// drain out the local queue
_exploit_task(w, t);
// steal and wait for tasks
if(_wait_for_task(w, t) == false) {
break;
}
}
}
catch(...) {
ptr = std::current_exception();
}
// call the user-specified epilogue function
if(_worker_interface) {
_worker_interface->scheduler_epilogue(w, ptr);
}
});
}
}
功能:
- _spawn 方法负责创建并启动线程池中的工作线程。
2.1 循环创建线程
for(size_t id = 0; id < N; ++id) {
- 遍历所有线程 ID(从 0 到 N-1),为每个线程进行初始化。
2.2 初始化线程相关属性
_workers[id]._id = id;
_workers[id]._vtm = id;
_workers[id]._executor = this;
_workers[id]._waiter = &_notifier._waiters[id];
- _id 和 _vtm:
- 分别表示线程的唯一标识符和虚拟线程管理器标识符。
- _executor:
- 指向当前 Executor 实例,以便线程可以访问全局资源。
- _waiter:
- 指向与该线程关联的通知器对象,用于线程间的同步。
- 创建线程
_workers[id]._thread = std::thread([&, &w=_workers[id]] () {
- 使用 std::thread 创建一个新线程,并绑定一个 lambda 函数作为线程的执行体。
线程执行体
2.3 初始化随机数生成器
w._rdgen.seed(static_cast<std::default_random_engine::result_type>(
std::hash<std::thread::id>()(std::this_thread::get_id()))
);
- 初始化线程的随机数生成器 _rdgen,用于工作窃取算法(work-stealing)。
- 种子基于当前线程的 ID。
2.4 调用 scheduler_prologue
if(_worker_interface) {
_worker_interface->scheduler_prologue(w);
}
- 如果存在自定义的 WorkerInterface,则调用其 scheduler_prologue 方法。
- 这允许用户在进入任务调度循环之前执行一些自定义逻辑。
2.5 任务调度循环
Node* t = nullptr;
std::exception_ptr ptr = nullptr;
try {
while(1) {
// 尝试从本地队列中获取任务
_exploit_task(w, t);
// 如果本地队列为空,尝试从其他线程的任务队列中窃取任务
if(_wait_for_task(w, t) == false) {
break;
}
}
} catch(...) {
ptr = std::current_exception();
}
2.6. _exploit_task函数
// Procedure: _exploit_task
inline void Executor::_exploit_task(Worker& w, Node*& t) {
while(t) {
_invoke(w, t);
t = w._wsq.pop();
}
}
// Procedure: _invoke
inline void Executor::_invoke(Worker& worker, Node* node) {
#define TF_INVOKE_CONTINUATION() \
if (cache) { \
node = cache; \
goto begin_invoke; \
}
begin_invoke:
Node* cache {nullptr};
// if this is the second invoke due to preemption, directly jump to invoke task
if(node->_nstate & NSTATE::PREEMPTED) {
goto invoke_task;
}
// if the work has been cancelled, there is no need to continue
if(node->_is_cancelled()) {
_tear_down_invoke(worker, node, cache);
TF_INVOKE_CONTINUATION();
return;
}
// if acquiring semaphore(s) exists, acquire them first
if(node->_semaphores && !node->_semaphores->to_acquire.empty()) {
SmallVector<Node*> waiters;
if(!node->_acquire_all(waiters)) {
_schedule(worker, waiters.begin(), waiters.end());
return;
}
}
invoke_task:
SmallVector<int> conds;
// switch is faster than nested if-else due to jump table
switch(node->_handle.index()) {
// static task
case Node::STATIC:{
_invoke_static_task(worker, node);
}
break;
// runtime task
case Node::RUNTIME:{
if(_invoke_runtime_task(worker, node)) {
return;
}
}
break;
// subflow task
case Node::SUBFLOW: {
if(_invoke_subflow_task(worker, node)) {
return;
}
}
break;
// condition task
case Node::CONDITION: {
_invoke_condition_task(worker, node, conds);
}
break;
// multi-condition task
case Node::MULTI_CONDITION: {
_invoke_multi_condition_task(worker, node, conds);
}
break;
// module task
case Node::MODULE: {
if(_invoke_module_task(worker, node)) {
return;
}
}
break;
// async task
case Node::ASYNC: {
if(_invoke_async_task(worker, node)) {
return;
}
_tear_down_async(worker, node, cache);
TF_INVOKE_CONTINUATION();
return;
}
break;
// dependent async task
case Node::DEPENDENT_ASYNC: {
if(_invoke_dependent_async_task(worker, node)) {
return;
}
_tear_down_dependent_async(worker, node, cache);
TF_INVOKE_CONTINUATION();
return;
}
break;
// monostate (placeholder)
default:
break;
}
// if releasing semaphores exist, release them
if(node->_semaphores && !node->_semaphores->to_release.empty()) {
SmallVector<Node*> waiters;
node->_release_all(waiters);
_schedule(worker, waiters.begin(), waiters.end());
}
// Reset the join counter with strong dependencies to support cycles.
// + We must do this before scheduling the successors to avoid race
// condition on _predecessors.
// + We must use fetch_add instead of direct assigning
// because the user-space call on "invoke" may explicitly schedule
// this task again (e.g., pipeline) which can access the join_counter.
node->_join_counter.fetch_add(
node->num_predecessors() - (node->_nstate & ~NSTATE::MASK), std::memory_order_relaxed
);
// acquire the parent flow counter
auto& join_counter = (node->_parent) ? node->_parent->_join_counter :
node->_topology->_join_counter;
// Invoke the task based on the corresponding type
switch(node->_handle.index()) {
// condition and multi-condition tasks
case Node::CONDITION:
case Node::MULTI_CONDITION: {
for(auto cond : conds) {
if(cond >= 0 && static_cast<size_t>(cond) < node->_num_successors) {
auto s = node->_edges[cond];
// zeroing the join counter for invariant
s->_join_counter.store(0, std::memory_order_relaxed);
join_counter.fetch_add(1, std::memory_order_relaxed);
_update_cache(worker, cache, s);
}
}
}
break;
// non-condition task
default: {
for(size_t i=0; i<node->_num_successors; ++i) {
//if(auto s = node->_successors[i]; --(s->_join_counter) == 0) {
if(auto s = node->_edges[i]; s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
join_counter.fetch_add(1, std::memory_order_relaxed);
_update_cache(worker, cache, s);
}
}
}
break;
}
// clean up the node after execution
_tear_down_invoke(worker, node, cache);
TF_INVOKE_CONTINUATION();
}
- 尝试从当前线程的本地任务队列中获取任务。
2.7. _wait_for_task函数
// Function: _wait_for_task
inline bool Executor::_wait_for_task(Worker& w, Node*& t) {
explore_task:
if(_explore_task(w, t) == false) {
return false;
}
// Go exploit the task if we successfully steal one.
if(t) {
return true;
}
// Entering the 2PC guard as all queues should be empty after many stealing attempts.
_notifier.prepare_wait(w._waiter);
// Condition #1: buffers should be empty
for(size_t vtm=0; vtm<_buffers.size(); ++vtm) {
if(!_buffers._buckets[vtm].queue.empty()) {
_notifier.cancel_wait(w._waiter);
w._vtm = vtm + _workers.size();
goto explore_task;
}
}
// Condition #2: worker queues should be empty
// Note: We need to use index-based looping to avoid data race with _spawan
// which initializes other worker data structure at the same time
for(size_t vtm=0; vtm<w._id; ++vtm) {
if(!_workers[vtm]._wsq.empty()) {
_notifier.cancel_wait(w._waiter);
w._vtm = vtm;
goto explore_task;
}
}
// due to the property of the work-stealing queue, we don't need to check
// the queue of this worker
for(size_t vtm=w._id+1; vtm<_workers.size(); vtm++) {
if(!_workers[vtm]._wsq.empty()) {
_notifier.cancel_wait(w._waiter);
w._vtm = vtm;
goto explore_task;
}
}
// Condition #3: worker should be alive
#if __cplusplus >= TF_CPP20
if(w._done.test(std::memory_order_relaxed)) {
#else
if(w._done.load(std::memory_order_relaxed)) {
#endif
_notifier.cancel_wait(w._waiter);
return false;
}
// Now I really need to relinquish myself to others.
_notifier.commit_wait(w._waiter);
goto explore_task;
}
- 如果本地队列为空,则尝试从其他线程的任务队列中窃取任务(工作窃取机制)。
- 如果没有任务可执行且线程需要退出,则返回 false 并退出循环。
- 异常处理:
- 捕获任何未处理的异常,并将其存储在 `ptr` 中。
2.8 调用 scheduler_epilogue
if(_worker_interface) {
_worker_interface->scheduler_epilogue(w, ptr);
}
- 在任务调度循环结束后,调用 scheduler_epilogue 方法。
- 这允许用户在线程退出时执行清理或错误处理逻辑。
3. 关键点总结
线程池的核心逻辑
1. 线程初始化:
- 每个线程都有唯一的 ID 和相关的资源(如任务队列、通知器等)。
2. 任务调度:
- 使用 工作窃取算法(work-stealing)来提高任务分配的效率。
- 每个线程优先从自己的任务队列中获取任务;如果队列为空,则尝试从其他线程的任务队列中窃取任务。
3. 自定义接口:
- 提供了 WorkerInterface 接口,允许用户在任务调度前后插入自定义逻辑。
4. 异常处理:
- 捕获线程中的异常,并通过 scheduler_epilogue 方法进行处理。
性能优化
- 工作窃取:
- 工作窃取算法减少了线程空闲时间,提高了多核 CPU 的利用率。
- 随机化:
- 使用随机数生成器选择目标线程,避免多个线程同时竞争同一个任务队列。
4. 完整流程
1. 调用 Executor 构造函数,初始化线程池大小和其他资源。
2. 调用 _spawn 方法,创建并启动工作线程。
3. 每个工作线程进入任务调度循环,执行任务并处理异常。
4. 当任务流结束时,线程退出调度循环并执行清理逻辑。
5. 示例场景
假设我们有一个自定义的 WorkerInterface 实现,可以如下使用这个构造函数和 _spawn 方法:
#include <taskflow/taskflow.hpp>
#include <iostream>
// 自定义 WorkerInterface
class MyWorkerInterface : public tf::WorkerInterface {
public:
void scheduler_prologue(tf::Worker& w) override {
std::cout << "Worker " << w._id << " entering the task loop\n";
}
void scheduler_epilogue(tf::Worker& w, std::exception_ptr ptr) override {
if(ptr) {
try {
std::rethrow_exception(ptr);
} catch(const std::exception& e) {
std::cerr << "Worker " << w._id << " caught exception: " << e.what() << "\n";
}
} else {
std::cout << "Worker " << w._id << " exiting the task loop\n";
}
}
};
int main() {
// 创建一个自定义的 WorkerInterface
auto custom_worker = std::make_shared<MyWorkerInterface>();
// 创建一个具有 4 个线程的 Executor,并传入自定义的 WorkerInterface
tf::Executor executor(4, custom_worker);
// 创建一个任务流
tf::Taskflow taskflow;
taskflow.emplace([]() { std::cout << "Task executed\n"; });
// 执行任务流
executor.run(taskflow).wait();
return 0;
}
这段代码展示了如何通过自定义 WorkerInterface 和线程池大小来创建一个灵活的 Executor 实例,并实现了任务调度和异常处理的完整流程。
6. _invoke()函数待...