@@ -5,12 +5,13 @@ you need to dispatch it to threads for execution
55In this tutorial, we will show you how to execute a
66task dependency graph.
77
8- + [ Graph and Topology] ( #Graph-and-Topology )
9- + [ Blocking Execution] ( #Blocking-Execution )
10- + [ Non-blocking Execution] ( #Non-blocking-Execution )
11- + [ Wait on Topologies] ( #Wait-on-Topologies )
12- + [ Example 1: Multiple Dispatches] ( #Example-1-Multiple-Dispatches )
13- + [ Example 2: Connect Two Dependency Graphs] ( #Example-2-Connect-Two-Dependency-Graphs )
8+ + [ Graph and Topology] ( #graph-and-topology )
9+ + [ Blocking Execution] ( #blocking-execution )
10+ + [ Non-blocking Execution] ( #non-blocking-execution )
11+ + [ Wait on Topologies] ( #wait-on-topologies )
12+ + [ Lifetime of a Graph] ( #lifetime-of-a-graph )
13+ + [ Example 1: Multiple Dispatches] ( #example-1-multiple-dispatches )
14+ + [ Example 2: Connect Two Dependency Graphs] ( #example-2-connect-two-dependency-graphs )
1415
1516# Graph and Topology
1617
@@ -138,6 +139,98 @@ After Line 11, there are two topologies in the taskflow object.
138139Calling the method ` wait_for_topologies ` blocks the
139140program until both graph complete.
140141
142+ # Lifetime of a Graph
143+
144+ In Cpp-Taskflow, the lifetime of a task sticks with its parent graph.
145+ The lifetime of a task mostly refers to the user-given callable objects,
146+ including those captured by a lambda expression.
147+ When a graph is destroyed, all of its tasks are destroyed.
148+ Consider the following example that uses
149+ [ std::shared_ptr] [ std::shared_ptr ] to demonstrate the lifetime of a graph and
150+ the impact on its task.
151+
152+ ``` cpp
153+ 1 : tf::Taskflow tf;
154+ 2 :
155+ 3 : auto ptr = std::make_shared<int >(0 );
156+ 4 :
157+ 5 : std::cout << " reference count before A and B: " << ptr.use_count() << ' \n ' ;
158+ 6 :
159+ 7 : auto A = tf.silent_emplace([ptr] () {
160+ 8: std::cout << "reference count at A: " << ptr.use_count() << '\n';
161+ 9: });
162+ 10:
163+ 11: auto B = tf.silent_emplace([ ptr] () {
164+ 12: std::cout << "reference count at B: " << ptr.use_count() << '\n';
165+ 13: });
166+ 14:
167+ 15: A.precede(B);
168+ 16:
169+ 17: std::cout << "reference count after A and B: " << ptr.use_count() << '\n';
170+ 18:
171+ 19: tf.wait_for_all();
172+ 20:
173+ 21: std::cout << "reference count after A and B: " << ptr.use_count() << '\n';
174+ ```
175+
176+ The output is as follows:
177+
178+ ```bash
179+ reference count before A and B: 1
180+ reference count after A and B: 3
181+ reference count at A: 3
182+ reference count at B: 3
183+ reference count after A and B: 1
184+ ```
185+
186+ In Line 5, we created a shared pointer object with one reference count on itself.
187+ After Line 7-13, the reference count increases by two because
188+ task A and task B both capture a copy of the shared pointer.
189+ The lifetime of a task has nothing to do with its dependency constraints,
190+ as shown in Line 8, Line 12, and Line 17.
191+ However, Line 19 dispatches the graph to threads and cleans up its data structure
192+ upon finish, including all associated tasks.
193+ Therefore, the reference count in Line 21 drops down to one (owner at Line 3).
194+
195+ Now let's use the same example but dispatch the graph asynchronously.
196+
197+ ``` cpp
198+ 1 : tf::Taskflow tf;
199+ 2 :
200+ 3 : auto ptr = std::make_shared<int >(0 );
201+ 4 :
202+ 5 : std::cout << " reference count before A and B: " << ptr.use_count() << ' \n ' ;
203+ 6 :
204+ 7 : auto A = tf.silent_emplace([ptr] () {
205+ 8: std::cout << "reference count at A: " << ptr.use_count() << '\n';
206+ 9: });
207+ 10:
208+ 11: auto B = tf.silent_emplace([ ptr] () {
209+ 12: std::cout << "reference count at B: " << ptr.use_count() << '\n';
210+ 13: });
211+ 14:
212+ 15: A.precede(B);
213+ 16:
214+ 17: std::cout << "reference count after A and B: " << ptr.use_count() << '\n';
215+ 18:
216+ 19: tf.dispatch().get(); // dispatch the graph without destroying it
217+ 20:
218+ 21: std::cout << "reference count after A and B: " << ptr.use_count() << '\n';
219+ ```
220+
221+ In Line 19, we replace `wait_for_all` with `disaptch` to dispatch the graph asynchronously
222+ without cleaning up its data structures upon completion.
223+ In other words, task A and task B remains un-destructed after Line 19,
224+ and the reference count at this point remains three.
225+
226+ ```bash
227+ reference count before A and B: 1
228+ reference count after A and B: 3
229+ reference count at A: 3
230+ reference count at B: 3
231+ reference count after A and B: 3
232+ ```
233+
141234# Example 1: Multiple Dispatches
142235
143236The example below demonstrates how to create multiple task dependency graphs and
@@ -253,3 +346,7 @@ The result of the variable `sum` ends up being the summation over
253346the integer sequence ` [0, 1, 2, ..., 1024) ` .
254347
255348
349+ * * *
350+
351+ [ std::shared_ptr ] : https://en.cppreference.com/w/cpp/memory/shared_ptr
352+
0 commit comments