创建完一个Taskflow,还需要将其提交给线程池执行,Executor就是执行该动作的对象。
执行一个Taskflow
Executor 是一个线程安全对象,管理一个具有work-stealing功能的线程池,当运行一个Taskflow,Executor 就会创建一个拓扑结构,来跟踪Taskflow的执行状态。
tf::Executor提供了一组run_*方法,tf::Executor::run,tf::Executor::run_n和tf::Executor::run_until运行任务流一次,多次,或直到给定的谓词求值为true。
#include <taskflow/taskflow.hpp>
void print_str(std::string const& str) {
std::cout<<str<<std::endl;
}
int main() {
tf::Executor executor;
tf::Taskflow taskflow;
// 创建三个task对象
auto [A, B, C] = taskflow.emplace(
[](){ std::cout << "Task A" << std::endl; },
[](){ std::cout << "Task B" << std::endl; },
[](){ std::cout << "Task C" << std::endl; }
);
// 构建依赖关系, A在B和C之前执行
A.precede(B, C);
// 异步提交,返回一个future
tf::Future<void> fu = executor.run(taskflow);
// fu.get(); // 等待任务执行完成,获取返回值,抛出异常
fu.wait(); // 等待任务执行完成,仅等待,不获取返回值,不抛出异常
// 执行一次,并在结束后输出end of 1 run
executor.run(taskflow, [](){print_str("\tend of 1 run");}).wait();
// 执行四次,并等待四次都结束
executor.run_n(taskflow, 4);
executor.wait_for_all();
// 执行四次,并在结束后输出end of 4 runs
executor.run_n(taskflow, 4, [](){ std::cout << "\tend of 4 runs"; }).wait();
// 执行直到满足条件,并在结束后输出end of 10 runs
executor.run_until(taskflow, [cnt=0] () mutable { return ++cnt == 10; }).wait();
return 0;
}
其中executor会根据run提交的顺序执行
executor.run(taskflow); // execution 1
executor.run_n(taskflow, 10); // execution 2
executor.run(taskflow); // execution 3
executor.wait_for_all(); // execution 1 -> execution 2 -> execution 3
同时,用户需要关注taskflow在执行期间的生命周期,如果在执行未完成前,taskflow资源回收,会导致未定义行为,当然,在执行期间修改(如添加新task)也是危险行为,会产生意料之外的影响。
转移Taskflow的 所有权
如果不想管理一个taskflow的生命周期,那么可以选择将其移交给executor来管理,当然,被移交后的taskflow无法再次被提交。
#include <algorithm>
#include <taskflow/taskflow.hpp>
void print_str(std::string const& str) {
std::cout<<str<<std::endl;
}
int main() {
tf::Executor executor;
tf::Taskflow taskflow;
// 创建三个task对象
auto [A, B, C] = taskflow.emplace(
[](){ std::cout << "Task A" << std::endl; },
[](){ std::cout << "Task B" << std::endl; },
[](){ std::cout << "Task C" << std::endl; }
);
// 构建依赖关系, A在B和C之前执行
A.precede(B, C);
// 现在taskflow 拥有3个task对象, 但是它们还没有被调度执行
assert(taskflow.num_tasks() == 3);
// 移交taskflow的所有权
executor.run(std::move(taskflow));
// taskflow被移交给executor, 所以它不再拥有task对象
assert(taskflow.num_tasks() == 0);
// 等待executor执行完所有的task
executor.wait_for_all();
return 0;
}
同样,不要转移一个正在被执行的taskflow,会导致难以预料的未定义行为,在移交taskflow所有权之前,要确保executor上的所有任务均已完成。
在executor接管一个taskflow,且该taskflow执行完成后,executor会自动释放该taskflow的资源。
tf::Executor::corun:后续再研究
还可以使用多线程提交taskflows,因为executor是线程安全的,所以这个操作被允许,但是具体taskflow的执行顺序就是未知的了。
1: tf::Executor executor;
2:
3: for(int i=0; i<10; ++i) {
4: std::thread([i, &](){
5: // ... modify my taskflow at i
6: executor.run(taskflows[i]); // run my taskflow at i
7: }).detach();
8: }
9:
10: executor.wait_for_all();
工作线程id 查询
executor可以查询处理每个task的具体线程id,如果当前线程不是worker 线程,返回-1:
#include <taskflow/taskflow.hpp>
void print_str(std::string const& str) {
std::cout<<str<<std::endl;
}
int main() {
tf::Executor executor(4);
tf::Taskflow taskflow;
// 主线程不在executor的线程池中
assert(executor.this_worker_id() == -1);
auto func = [&executor](){
int id = executor.this_worker_id(); // 执行该task的线程id
assert(id >= 0);
std::cout<<"taskflow.emplace() in thread "<<id<<std::endl;
};
taskflow.emplace(func);
taskflow.emplace(func);
taskflow.emplace(func);
executor.run(taskflow).wait();
return 0;
}
观察者接口
Taskflow 提供ObserverInterface类,用来实现executor在每一个Task执行前和执行后进行的操作,通过std::shared_ptr来维护其生命周期。
class ObserverInterface {
virtual ~ObserverInterface() = default;
virtual void set_up(size_t num_workers) = 0;
virtual void on_entry(tf::WorkerView worker_view, tf::TaskView task_view) = 0;
virtual void on_exit(tf::WorkerView worker_view, tf::TaskView task_view) = 0;
};
简单样例:
#include <memory>
#include <taskflow/taskflow.hpp>
void print_str(std::string const& str) {
std::cout<<str<<std::endl;
}
struct MyObserver : public tf::ObserverInterface {
MyObserver() {
print_str("construct MyObserver");
}
// 相当于init
void set_up(size_t num_workers) override final {
std::cout<<"MyObserver::set_up("<<num_workers<<")"<<std::endl;
}
// task 执行前调用
void on_entry(tf::WorkerView w, tf::TaskView tv) override final {
std::ostringstream oss;
oss << "worker " << w.id() << " ready to run " << tv.name() << '\n';
std::cout << oss.str();
}
// task 执行后调用
void on_exit(tf::WorkerView w, tf::TaskView tv) override final {
std::ostringstream oss;
oss << "worker " << w.id() << " finished " << tv.name() << '\n';
std::cout << oss.str();
}
};
int main() {
tf::Executor executor(4);
tf::Taskflow taskflow;
auto A = taskflow.emplace([] () { std::cout << "1\n"; }).name("A");
auto B = taskflow.emplace([] () { std::cout << "2\n"; }).name("B");
auto C = taskflow.emplace([] () { std::cout << "3\n"; }).name("C");
auto D = taskflow.emplace([] () { std::cout << "4\n"; }).name("D");
auto E = taskflow.emplace([] () { std::cout << "5\n"; }).name("E");
auto F = taskflow.emplace([] () { std::cout << "6\n"; }).name("F");
auto G = taskflow.emplace([] () { std::cout << "7\n"; }).name("G");
auto H = taskflow.emplace([] () { std::cout << "8\n"; }).name("H");
// 创建一个观察者对象指针,由shared_ptr管理其生命周期
// 并tie 到executor中
std::shared_ptr<MyObserver> my_observer = executor.make_observer<MyObserver>();
// 执行
executor.run(taskflow).wait();
// 移除观察者,这个接口好别扭啊。。。
executor.remove_observer(std::move(my_observer));
return 0;
}