Cpp-Taskflow
2.2.0
|
This chapter demonstrates how to create a task dependency graph–tf::Taskflow.
A task in Cpp-Taskflow is a callable object for which the operation std::invoke is applicable. It can be either a functor, a lambda expression, a bind expression, or a class objects with operator()
overloaded. All tasks are created from tf::Taskflow, the class that manages a task dependency graph and its tasks. Cpp-Taskflow provides two methods, tf::Taskflow::placeholder and tf::Taskflow::emplace to create a task.
Debrief:
Each time you create a task including an empty one, the taskflow object adds a node to the present graph and returns a task handle of type tf::Task. A task handle is a lightweight object that wraps up a particular node in a graph and provides a set of methods for you to assign different attributes to the task such as adding dependencies, naming, and assigning a new work.
Debrief:
Cpp-Taskflow uses the general-purpose polymorphic function wrapper std::function to store and invoke any callable target in a task. You need to follow its contract to create a task. For instance, the callable object must be copy constructible.
Cpp-Taskflow uses C++ structured binding coupled with std::tuple to make it simple to create multiple tasks at one time.
A task lives with its graph and belongs to only a graph at a time, and is not destroyed until the graph gets cleaned up. The lifetime of a task refers to the user-given callable object, including captured values. As long as the graph is alive, all the associated tasks exist. It is your responsibility to keep tasks and graph alive during their execution.
Putting everything together, the example below creates a simple task dependency graph of four dependent tasks.
Debrief:
This example demonstrates how to modify a task's attributes using methods defined in the task handler.
The output of this program looks like the following:
Debrief:
You can change the name and work of a task at anytime before running the graph. The later assignment overwrites the previous values.
A powerful feature of tf::Taskflow is its composable interface. You can break down a large parallel workload into smaller pieces each designed to run a specific task dependency graph. This largely facilitates the modularity of writing a parallel task program.
Debrief:
The task created from Taskflow::composed_of is a module task that runs on a taskflow. A module task does not owns any taskflow but maintains a soft mapping to use during its execution context. You can create multiple module tasks from the same taskflow but only one module task can run at one time. For example, the following composition is valid. Even though the two module tasks module1
and module2
refer to the same taskflow F1
, the dependency link prevents F1
from multiple executions at the same time.
However, the following composition is invalid. Both module tasks refer to the same taskflow. They can not run at the same time because they are associated with the same graph.