Taskflow:执行器(Executor)

创建完一个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;
}
  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值