Executor类--初始化流程

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()函数待...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值