GithubHelp home page GithubHelp logo

cpp-taskflow's Introduction

Cpp-Taskflow

Build Status Gitter chat License: MIT

A fast C++ header-only library to develop complex and parallel task dependency graphs.

Why Cpp-Taskflow?

Cpp-Taskflow helps you build efficient computational graphs quickly using modern C++17. It is by far faster and more expressive than existing libraries such as OpenMP Tasking and TBB FlowGraph.

Get Started with Cpp-Taskflow

The following example simple.cpp contains the basic syntax you need to use Cpp-Taskflow.

// TaskA---->TaskB---->TaskD
// TaskA---->TaskC---->TaskD

#include "taskflow.hpp"

int main(){
  
  tf::Taskflow tf(std::thread::hardware_concurrency());

  auto [A, B, C, D] = tf.silent_emplace(
    [] () { std::cout << "TaskA\n"; },
    [] () { std::cout << "TaskB\n"; },
    [] () { std::cout << "TaskC\n"; },
    [] () { std::cout << "TaskD\n"; }
  );  

  A.precede(B);  // B runs after A
  A.precede(C);  // C runs after A
  B.precede(D);  // D runs after B
  C.precede(D);  // C runs after D

  tf.wait_for_all();  // block until all tasks finish

  return 0;
}

Compile and run the code with the following commands:

~$ g++ simple.cpp -std=c++1z -O2 -lpthread -o simple
~$ ./simple
TaskA
TaskC  <-- concurrent with TaskB
TaskB  <-- concurrent with TaskC
TaskD

Create a Taskflow Graph

Cpp-Taskflow has very expressive, neat, and clear methods to create complex dependency graphs. Most applications are developed through the following three steps.

Step 1: Create a Task

To start a task dependency graph, create a taskflow object and specify the number of working threads in a shared thread pool to carry out tasks.

tf::Taskflow tf(std::max(1u, std::thread::hardware_concurrency()));

Create a task via the method emplace and get a pair of Task and future.

auto [A, F] = tf.emplace([](){ std::cout << "Task A\n"; return 1; });

Or create a task via the method silent_emplace, if you don't need a future to retrieve the result.

auto [A] = tf.silent_emplace([](){ std::cout << "Task A\n"; });

Both methods implement variadic templates and can take arbitrary numbers of arguments to create multiple tasks at one time.

auto [A, B, C, D] = tf.silent_emplace(
  [] () { std::cout << "Task A\n"; },
  [] () { std::cout << "Task B\n"; },
  [] () { std::cout << "Task C\n"; },
  [] () { std::cout << "Task D\n"; }
);

Step 2: Define Task Dependencies

Once tasks are created in the pool, you need to specify task dependencies in a Directed Acyclic Graph (DAG) fashion. The class Task supports different methods for you to describe task dependencies.

Precede: Adding a preceding link forces one task to run ahead of one another.

A.precede(B);  // A runs before B.

Broadcast: Adding a broadcast link forces one task to run ahead of other(s).

A.broadcast(B, C, D);  // A runs before B, C, and D.

Gather: Adding a gathering link forces one task to run after other(s).

A.gather(B, C, D);  // A runs after B, C, and D.

Linearize: Linearizing a task sequence adds a preceding link to each adjacent pair.

tf.linearize(A, B, C, D);  // A runs before A, B runs before C, and C runs before D.

Step 3: Execute the Tasks

There are three methods to carry out a task dependency graph, dispatch, silent_dispatch, and wait_for_all.

auto future = tf.dispatch();  // non-blocking, returns with a future immediately.
tf.dispatch();                // non-blocking, no return

Calling wait_for_all will block until all tasks complete.

tf.wait_for_all();

Debug a Taskflow Graph

Concurrent programs are notoriously difficult to debug. We suggest (1) naming tasks and dumping the graph, and (2) starting with single thread before going multiple. Currently, Cpp-Taskflow supports GraphViz format.

// debug.cpp
tf::Taskflow tf(0);  // force the master thread to execute all tasks
auto A = tf.silent_emplace([] () { /* ... */ }).name("A");
auto B = tf.silent_emplace([] () { /* ... */ }).name("B");
auto C = tf.silent_emplace([] () { /* ... */ }).name("C");
auto D = tf.silent_emplace([] () { /* ... */ }).name("D");
auto E = tf.silent_emplace([] () { /* ... */ }).name("E");

A.broadcast(B, C, E); 
C.precede(D);
B.broadcast(D, E); 

std::cout << tf.dump();

Run the program and inspect whether dependencies are expressed in the right way.

~$ ./debug
digraph Taskflow {
  "A" -> "B"
  "A" -> "C"
  "A" -> "E"
  "B" -> "D"
  "B" -> "E"
  "C" -> "D"
}

There are a number of free GraphViz tools you could find online to visualize your Taskflow graph.

Taskflow with five tasks and six dependencies, generated by Viz.js.

API Reference

Taskflow

The class tf::Taskflow is the main place to create taskflow graphs and carry out task dependencies. The table below summarizes its commonly used methods.

Method Argument Return Description
emplace callable(s) task, future insert nodes to the graph to execute the given callables; results can be retrieved from the returned future object
silent_emplace callable(s) task insert nodes to the graph to execute the given callables
placeholder none task insert a node to the graph without any work; work can be assigned later
linearize task list none create a linear dependency in the given task list
parallel_for range, callable task pair apply the callable in parallel to each element in the given range and return a task pair as barriers; the range is specified in two iterators
dispatch none future dispatch the current graph and return a shared future to block on completeness
silent_dispatch none none dispatch the current graph
wait_for_all none none dispatch the current graph and block until all graphs including previously dispatched ones finish
num_nodes none size return the number of nodes in the current graph
num_workers none size return the number of working threads in the pool
num_topologies none size return the number of dispatched graphs
dump none string dump the current graph to a string of GraphViz format

Task

Each tf::Taskflow::Task object is a light-weight handle for you to create dependencies in its associated graph. The table below summarizes its methods.

Method Argument Return Description
name string self assign a human-readable name to the task
work callable self assign a work of a callable object to the task
precede task self enable this task to run before the given task
broadcast task list self enable this task to run before the given tasks
gather task list self enable this task to run after the given tasks
num_dependents none size return the number of dependents (inputs) of this task
num_successors none size return the number of successors (outputs) of this task

Caveats

While Cpp-Taskflow enables the expression of very complex task dependency graph that might contain thousands of task nodes and links, there are a few amateur pitfalls and mistakes to be aware of.

  • Having a cycle in a graph may result in running forever.
  • Trying to modify a dispatched task can result in undefined behavior.
  • Touching a taskflow from multiple threads are not safe.

The current version is known to work on most Linux distributions and OSX. We havn't found issues in a particular platform. Please report to us if any.

System Requirements

To use Cpp-Taskflow, you only need a C++17 compiler:

  • GNU C++ Compiler G++ v7.2 with -std=c++1z
  • Clang 5.0 C++ Compiler with -std=c++17

Compile Unit Tests and Examples

Cpp-Taskflow uses CMake to build examples and unit tests. We recommend using out-of-source build.

~$ cmake --version  # must be at least 3.9 or higher
~$ mkdir build
~$ cmake ../
~$ make 

Unit Tests

Cpp-Taskflow uses Doctest for unit tests.

~$ ./unittest/taskflow

Alternatively, you can use CMake's testing framework to run the unittest.

~$ cd build
~$ make test

Examples

The folder example/ contains several examples and is a great place to learn to use Cpp-Taskflow.

Get Involved

Contributors

  • Tsung-Wei Huang
  • Chun-Xun Lin
  • You!

License

Copyright © 2018, Tsung-Wei Huang, Chun-Xun Lin, and Martin Wong. Released under the MIT license.

cpp-taskflow's People

Contributors

tsung-wei-huang avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.