@@ -53,8 +53,8 @@ visit the [documentation][wiki] to learn more about Cpp-Taskflow.
5353Technical details can be referred to our [ IEEE IPDPS19 paper] [ IPDPS19 ] .
5454
5555:exclamation : Notice that starting at v2.2.0 (including this master branch)
56- we isolated the executor interface from Taskflow to improve programming model and performance.
57- This introduced a few breaks in using Cpp-Taskflow.
56+ we isolated the executor interface from Taskflow to improve the programming model and performance.
57+ This caused a few breaks in using Cpp-Taskflow.
5858Please refer to [ release-notes] ( https://cpp-taskflow.github.io/cpp-taskflow/release-2-2-0.html )
5959for adapting to this new change.
6060
@@ -154,28 +154,28 @@ b3.precede(T); // b3 runs before T
154154
155155# Create a Taskflow Graph
156156
157- Cpp-Taskflow has very expressive and neat methods to create dependency graphs.
157+ Cpp-Taskflow defines a very expressive API to create task dependency graphs.
158158Most applications are developed through the following three steps.
159159
160160## Step 1: Create a Task
161161
162- Create a taskflow object to start with a task dependency graph:
162+ Create a taskflow object to build a task dependency graph:
163163
164164``` cpp
165- tf::Taskflow tf ;
165+ tf::Taskflow taskflow ;
166166```
167167
168168A task is a callable object for which [ std::invoke] [ std::invoke ] is applicable.
169169Use the method ` emplace ` to create a task:
170170
171171``` cpp
172- tf::Task A = tf .emplace([](){ std::cout << "Task A\n"; });
172+ tf::Task A = taskflow .emplace([](){ std::cout << "Task A\n"; });
173173```
174174
175- You can create multiple tasks at one time.
175+ You can create multiple tasks at one time:
176176
177177```cpp
178- auto [A, B, C, D] = tf .emplace(
178+ auto [A, B, C, D] = taskflow .emplace(
179179 [] () { std::cout << "Task A\n"; },
180180 [] () { std::cout << "Task B\n"; },
181181 [] () { std::cout << "Task C\n"; },
@@ -185,68 +185,72 @@ auto [A, B, C, D] = tf.emplace(
185185
186186## Step 2: Define Task Dependencies
187187
188- Once tasks are created in the pool, you need to specify task dependencies in a
189- [ Directed Acyclic Graph (DAG)] ( https://en.wikipedia.org/wiki/Directed_acyclic_graph ) fashion.
188+ You can add dependency links between tasks to enforce one task run after another.
189+ The dependency links must be specified in a
190+ [ Directed Acyclic Graph (DAG)] ( https://en.wikipedia.org/wiki/Directed_acyclic_graph ) .
190191The handle ` Task ` supports different methods for you to describe task dependencies.
191192
192- ** Precede** : Adding a preceding link forces one task to run ahead of one another.
193+ ** Precede** : Adding a preceding link forces one task to run before another.
193194``` cpp
194195A.precede(B); // A runs before B.
195196```
196197
197- ** Gather** : Adding a gathering link forces one task to run after other(s) .
198+ ** Gather** : Adding a gathering link forces one task to run after another .
198199``` cpp
199200A.gather(B); // A runs after B
200201```
201202
202203## Step 3: Execute a Taskflow
203204
204- To execute a taskflow, you need to create an executor.
205- An executor manages a set of worker threads to execute each dependent tasks
206- through an efficient work-stealing algorithm.
205+ To execute a taskflow, you need to create an * executor* .
206+ An executor manages a set of worker threads to execute a taskflow
207+ through an efficient * work-stealing* algorithm.
207208
208209``` cpp
209- tf::Executor executor; // executor manages a set of worker threads
210+ tf::Executor executor;
210211```
211212
212- The executor class provides a rich set of methods to run a taskflow.
213- You can run a taskflow multiple times or until a stopping criteria is met.
214- These methods are non-blocking and will return a [ std::shared_future] [ std::shared_future ]
213+ The executor provides a rich set of methods to run a taskflow.
214+ You can run a taskflow one or multiple times, or until a stopping criteria is met.
215+ These methods are non-blocking and all return a [ std::shared_future] [ std::shared_future ]
215216to let you query the execution status.
216217
217218``` cpp
218- executor.run(tf ); // run the tf once
219- executor.run(tf , [](){ std::cout << "done 1 run\n"; } ); // run once with a callback
220- executor.run_n(tf , 4); // run four times
221- executor.run_n(tf , 4, [ ] ( ) { std::cout << "done 4 runs\n"; }); // run 4 times with a callback
219+ executor.run(taskflow ); // run the taskflow once
220+ executor.run(taskflow , [](){ std::cout << "done 1 run\n"; } ); // run once with a callback
221+ executor.run_n(taskflow , 4); // run four times
222+ executor.run_n(taskflow , 4, [ ] ( ) { std::cout << "done 4 runs\n"; }); // run 4 times with a callback
222223
223224// run n times until the predicate becomes true
224- executor.run_until(tf , [ counter=4] ( ) { return --counter == 0; } );
225+ executor.run_until(taskflow , [ counter=4] ( ) { return --counter == 0; } );
225226
226227// run n times until the predicate becomes true and invoke the callback on completion
227- executor.run_until(tf , [ counter=4] ( ) { return --counter == 0; },
228- [ ] ( ) { std::cout << "Execution finishes\n"; } );
228+ executor.run_until(taskflow , [ counter=4] ( ) { return --counter == 0; },
229+ [ ] ( ) { std::cout << "Execution finishes\n"; } );
229230```
230231
231232
232- You can call `wait_for_all` to block the executor until all associated tasks complete.
233+ You can call `wait_for_all` to block the executor until all associated taskflows complete.
233234
234235```cpp
235236executor.wait_for_all(); // block until all associated tasks finish
236237```
237238
238239Notice that executor does not own any taskflow.
239- It is your responsibility to keep a taskflow alive during its execution.
240+ It is your responsibility to keep a taskflow alive during its execution,
241+ or it can result in undefined behavior.
242+ In most applications, you need only one executor to run multiple taskflows
243+ each representing a specific part of your parallel decomposition.
240244
241245# Dynamic Tasking
242246
243247Another powerful feature of Taskflow is * dynamic* tasking.
244- A dynamic task is created during the execution of a taskflow.
248+ Dynamic tasks are those created during the execution of a taskflow.
245249These tasks are spawned by a parent task and are grouped together to a * subflow* graph.
246250The example below demonstrates how to create a subflow
247251that spawns three tasks at runtime.
248252
249- <img align =" right " src =" image/subflow_join.png " width =" 35 %" >
253+ <img align =" right " src =" image/subflow_join.png " width =" 30 %" >
250254
251255``` cpp
252256// create three regular tasks
@@ -273,7 +277,7 @@ By default, a subflow graph joins to its parent node.
273277This guarantees a subflow graph to finish before the successors of
274278its parent node.
275279You can disable this feature by calling ` subflow.detach() ` .
276- Detaching the above subflow will result in the following execution flow.
280+ For example, detaching the above subflow will result in the following execution flow:
277281
278282<img align =" right " src =" image/subflow_detach.png " width =" 35% " >
279283
@@ -295,16 +299,16 @@ tf::Task B = tf.emplace([] (tf::SubflowBuilder& subflow) {
295299
296300Cpp-Taskflow has an unified interface for static and dynamic tasking.
297301To create a subflow for dynamic tasking,
298- emplace a callable on one argument of type ` tf::SubflowBuilder ` .
302+ emplace a callable with one argument of type ` tf::SubflowBuilder ` .
299303
300304``` cpp
301305tf::Task A = tf.emplace([] (tf::SubflowBuilder& subflow) {});
302306```
303307
304308A subflow builder is a lightweight object that allows you to create
305- arbitrary dependency graphs on the fly .
309+ arbitrary dependency graphs at runtime .
306310All graph building methods defined in taskflow
307- can be used in a subflow builder.
311+ can be used in the subflow builder.
308312
309313``` cpp
310314tf::Task A = tf.emplace([] (tf::SubflowBuilder& subflow) {
@@ -317,7 +321,7 @@ tf::Task A = tf.emplace([] (tf::SubflowBuilder& subflow) {
317321});
318322```
319323
320- A subflow can also be nested or recursive. You can create another subflow from
324+ A subflow can be nested or recursive. You can create another subflow from
321325the execution of a subflow and so on.
322326
323327<img align =" right " src =" image/nested_subflow.png " width =" 25% " >
@@ -356,8 +360,8 @@ tf::Task A = tf.emplace([] (tf::SubflowBuilder& subflow) {
356360}); // subflow starts to run after the callable scope
357361```
358362
359- Detaching or Joining a subflow has different meaning in the ready status of
360- the future object referred to it .
363+ Detaching or joining a subflow has different meaning in the completion status of
364+ its parent node .
361365In a joined subflow,
362366the completion of its parent node is defined as when all tasks
363367inside the subflow (possibly nested) finish.
@@ -439,8 +443,8 @@ f2B.precede(f1_module_task);
439443f1_module_task.precede(f2C);
440444```
441445
442- The `composed_of` method returns a *module task* and you can use the `precede` and `gather`
443- methods to define its dependencies.
446+ Similarly, `composed_of` returns a task handle and you can use the same methods
447+ `precede` and `gather` to create dependencies.
444448You can compose a taskflow from multiple taskflows and use the result
445449to compose another taskflow and so on.
446450
@@ -463,19 +467,19 @@ You can dump it to a GraphViz format using the method `dump`.
463467
464468```cpp
465469// debug.cpp
466- tf::Taskflow tf ;
470+ tf::Taskflow taskflow ;
467471
468- tf::Task A = tf .emplace([] () {}).name("A");
469- tf::Task B = tf .emplace([] () {}).name("B");
470- tf::Task C = tf .emplace([] () {}).name("C");
471- tf::Task D = tf .emplace([] () {}).name("D");
472- tf::Task E = tf .emplace([] () {}).name("E");
472+ tf::Task A = taskflow .emplace([] () {}).name("A");
473+ tf::Task B = taskflow .emplace([] () {}).name("B");
474+ tf::Task C = taskflow .emplace([] () {}).name("C");
475+ tf::Task D = taskflow .emplace([] () {}).name("D");
476+ tf::Task E = taskflow .emplace([] () {}).name("E");
473477
474478A.precede(B, C, E);
475479C.precede(D);
476480B.precede(D, E);
477481
478- tf .dump(std::cout);
482+ taskflow .dump(std::cout);
479483```
480484
481485Run the program and inspect whether dependencies are expressed in the right way.
@@ -561,7 +565,7 @@ You can pan or zoom in/out the timeline to get into a detailed view.
561565
562566# API Reference
563567
564- The official [documentation][wiki] explains the complete list of
568+ The official [documentation][wiki] explains a complete list of
565569Cpp-Taskflow API.
566570In this section, we highlight a short list of commonly used methods.
567571
@@ -583,7 +587,7 @@ The table below summarizes a list of commonly used methods.
583587
584588### *emplace/placeholder*
585589
586- You can use `emplace` to create a task for a target callable.
590+ You can use `emplace` to create a task from a target callable.
587591
588592```cpp
589593// create a task through emplace
@@ -644,7 +648,7 @@ auto [S, T] = tf.parallel_for(
644648By default, taskflow performs an even partition over worker threads
645649if the group size is not specified (or equal to 0).
646650
647- In addition to range-based iterator, parallel\_for has another overload on an index-based loop.
651+ In addition to range-based iterator, `parallel_for` has another overload of index-based loop.
648652The first three argument to this overload indicates
649653starting index, ending index (exclusive), and step size.
650654
@@ -678,7 +682,7 @@ auto [S, T] = tf.parallel_for(
678682
679683The method `reduce` creates a subgraph that applies a binary operator to a range of items.
680684The result will be stored in the referenced `res` object passed to the method.
681- It is your responsibility to assign it a correct initial value to reduce.
685+ It is your responsibility to assign a correct initial value to the reduce call .
682686
683687<img align="right" width="45%" src="image/reduce.png">
684688
@@ -702,15 +706,15 @@ auto [S, T] = tf.transform_reduce(v.begin(), v.end(), min,
702706);
703707```
704708
705- By default, all reduce methods distribute the workload evenly across threads .
709+ By default, all reduce methods distribute the workload evenly across `std::thread::hardware_concurrency` .
706710
707711## Task API
708712
709713Each time you create a task, the taskflow object adds a node to the present task dependency graph
710714and return a *task handle* to you.
711715A task handle is a lightweight object that defines a set of methods for users to
712716access and modify the attributes of the associated task.
713- The table below summarizes the list of commonly used methods.
717+ The table below summarizes a list of commonly used methods.
714718
715719| Method | Argument | Return | Description |
716720| -------------- | ----------- | ------ | ----------- |
@@ -739,7 +743,7 @@ A.work([] () { std::cout << "hello world!"; });
739743
740744### *precede*
741745
742- The method `precede` is the basic building block to add a precedence between two tasks .
746+ The method `precede` lets you add a preceding link from self to a task .
743747
744748<img align="right" width="20%" src="image/precede.png">
745749
@@ -760,7 +764,7 @@ A.precede(B, C, D, E);
760764
761765### * gather*
762766
763- The method ` gather ` lets you add multiple precedences to a task.
767+ The method ` gather ` lets you add a preceding link from a task to self .
764768
765769<img align =" right " width =" 30% " src =" image/gather.png " >
766770
@@ -785,33 +789,38 @@ The table below summarizes a list of commonly used methods.
785789
786790### * Executor*
787791
788- The constructor of tf::Executor can take an unsigned integer to create N worker threads.
792+ The constructor of tf::Executor takes an unsigned integer to
793+ initialize the executor with ` N ` worker threads.
789794
790795``` cpp
791796tf::Executor executor (8); // create an executor of 8 worker threads
792797```
793798
794- By default, we use `std::thread::hardware_concurrency` to decide the number of worker threads.
799+ The default value uses `std::thread::hardware_concurrency`
800+ to decide the number of worker threads.
795801
796802### *run/run_n/run_until*
797803
798804The run series are non-blocking call to execute a taskflow graph.
799- Issuing multiple runs on the same taskflow will automatically synchronize the execution.
805+ Issuing multiple runs on the same taskflow will automatically synchronize
806+ to a sequential chain of executions.
800807
801808```cpp
802809executor.run(taskflow); // run a graph once
803810executor.run_n(taskflow, 5); // run a graph five times
804811executor.run_n(taskflow, my_pred); // keep running a graph until the predicate becomes true
805812```
806813
814+ The first run finishes before the second run, and the second run finishes before the third run.
815+
807816# Caveats
808817
809818While Cpp-Taskflow enables the expression of very complex task dependency graph that might contain
810819thousands of task nodes and links, there are a few amateur pitfalls and mistakes to be aware of.
811820
812821+ Having a cycle in a graph may result in running forever
822+ + Destructing a taskflow while it is running in one execution results in undefined behavior
813823+ Trying to modify a running task can result in undefined behavior
814- + Touching a taskflow from multiple threads are not safe
815824
816825Cpp-Taskflow is known to work on Linux distributions, MAC OSX, and Microsoft Visual Studio.
817826Please [ let me know] [ email me ] if you found any issues in a particular platform.
0 commit comments