<!-- iframe showing the search results (closed by default) -->
<divid="MSearchResultsWindow">
<iframesrc="javascript:void(0)" frameborder="0"
name="MSearchResults" id="MSearchResults">
</iframe>
</div>
<divclass="header">
<divclass="headertitle">
<divclass="title">C6: Manage Threads and Executor </div></div>
</div><!--header-->
<divclass="contents">
<divclass="textblock"><p>We discuss in this chapter the thread management and task execution schemes in Cpp-Taskflow. We will go through the concept of <em>thread</em>, <em>ownership</em>, and <em>executor</em> in Cpp-Taskflow.</p>
<p>Cpp-Taskflow defines a strict relationship between the master and workers. Master is the thread that creates the executor object and workers are threads that invoke the callable target of a task. Each executor manages its own set of worker threads in a shared pool to schedule tasks. By default, Cpp-Taskflow uses <aclass="elRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a> to decide the number of worker threads and <aclass="elRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/thread/thread/get_id.html">std::thread::get_id</a> to identify the ownership between the master and workers.</p>
<divclass="fragment"><divclass="line"><aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/io/basic_ostream.html">std::cout</a> << <aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a>() << std::endl; <spanclass="comment">// 8, for example</span></div><divclass="line"><aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/io/basic_ostream.html">std::cout</a> << <aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/thread/thread/get_id.html">std::thread::get_id</a>() << std::endl; <spanclass="comment">// master thread id</span></div><divclass="line"><aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> exe1; <spanclass="comment">// create an executor with 8 workers</span></div><divclass="line"><aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> exe2(4); <spanclass="comment">// create an executor with 4 workers</span></div></div><!-- fragment --><p>In the above example, the master thread owns both executor objects. The first executor <code>exe1</code> creates eight (default by <aclass="elRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a>) worker threads and the second executor <code>exe2</code> creates four worker threads. Including the master thread, there will be a total of 1 + 8 + 4 = 13 threads running in this program. If you create an executor with zero workers, the master will carry out all the tasks by itself. That is, using one worker and zero worker are conceptually equivalent to each other since they both end up using one thread to run all tasks (see the snippet below).</p>
<divclass="fragment"><divclass="line"><aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> exe1(0); <spanclass="comment">// one master, zero worker (master to run tasks)</span></div><divclass="line"><aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> exe2(1); <spanclass="comment">// one master, one worker (one thread to run tasks)</span></div></div><!-- fragment --><p>In general, the master thread is where you start the <code>main</code> function (main thread), while the worker threads are transparently maintained by its own executor. Cpp-Taskflow's executor implements a very efficient work-stealing algorithm to schedule the execution of tasks.</p>
<h1><aclass="anchor" id="C6ThreadSafety"></a>
Thread Safety</h1>
<p><aclass="el" href="classtf_1_1Executor.html" title="The executor class to run a taskflow graph. ">tf::Executor</a> is <em>NOT</em> thread-safe. Touching an executor from multiple threads can result in <em>undefined behavior</em>. Notice that this is different from running multiple taskflows on a same executor which is valid. Thread safety has nothing to do with the master nor the workers. It is completely safe to access an executor as long as only one thread presents at a time. However, we strongly recommend users to acknowledge the definition of the master and the workers, and separate the program control flow accordingly. Having a clear thread ownership can greatly reduce the chance of buggy implementations and undefined behaviors.</p>
<p>Inspecting the thread activities is very important for performance analysis. It allows you to know when each task starts and ends participating in the task scheduling. Cpp-Taskflow provides a default observer class <aclass="el" href="classtf_1_1ExecutorObserver.html" title="Default executor observer to dump the execution timelines. ">tf::ExecutorObserver</a> for this purpose. The following example shows how to create an observer from an executor.</p>
<divclass="fragment"><divclass="line"><aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div><divclass="line"><aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>* observer = executor.<aclass="code" href="classtf_1_1Executor.html#a7f43cde72d3e0a17e2d006cbe7a41ff3">make_observer</a><<aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>>();</div></div><!-- fragment --><p>Note that each executor can only have an observer at a time. An observer will automatically record the start and end timestamps of each executed task. Users can query, dump or remove the timestamps through the <aclass="el" href="classtf_1_1ExecutorObserver.html#a93d51307198abb9a1fc00a14904c3dd0" title="get the number of total tasks in the observer ">tf::ExecutorObserver::num_tasks</a>, <aclass="el" href="classtf_1_1ExecutorObserver.html#a20f77a06e0f10dc67f7a5742add5b00a" title="dump the timelines in JSON format to an ostream ">tf::ExecutorObserver::dump</a> and <aclass="el" href="classtf_1_1ExecutorObserver.html#adc78a004eaa25022a20fd16a35f607ce" title="clear the timeline data ">tf::ExecutorObserver::clear</a> methods.</p>
<divclass="fragment"><divclass="line"> 1. <aclass="code" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div><divclass="line"> 2. <aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>* observer = executor.<aclass="code" href="classtf_1_1Executor.html#a7f43cde72d3e0a17e2d006cbe7a41ff3">make_observer</a><<aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>>();</div><divclass="line"> 3.</div><divclass="line"> 4. executor.<aclass="code" href="classtf_1_1Executor.html#a81f35d5b0a20ac0646447eb80d97c0aa">run</a>(taskflow).get(); <spanclass="comment">// do something</span></div><divclass="line"> 5.</div><divclass="line"> 6. <spanclass="comment">// Query the total number of tasks (number of timestamp pairs)</span></div><divclass="line"> 7. <spanclass="keyword">auto</span> num_tasks = observer-><aclass="code" href="classtf_1_1ExecutorObserver.html#a93d51307198abb9a1fc00a14904c3dd0">num_tasks</a>();</div><divclass="line"> 8.</div><divclass="line"> 9. <spanclass="comment">// Dump the timeline data in JSON format </span></div><divclass="line">10. <aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/string/basic_string.html">std::string</a> timelines_in_json = observer-><aclass="code" href="classtf_1_1ExecutorObserver.html#a20f77a06e0f10dc67f7a5742add5b00a">dump</a>();</div><divclass="line">11. </div><divclass="line">12. <spanclass="comment">// Clear the timeline data</span></div><divclass="line">13. observer-><aclass="code" href="classtf_1_1ExecutorObserver.html#adc78a004eaa25022a20fd16a35f607ce">clear</a>();</div></div><!-- fragment --><p>Debrief:</p>
<ul>
<li>Line 2-4 creates an observer and a task dependency graph with four tasks and dispatch the tasks to execution. </li>
<li>Line 7 query the total number of tasks (number of timestamp pair) through observer </li>
<li>Line 10 dump the timestamps to a <aclass="elRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/doxygen/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/string/basic_string.html">std::string</a> in JSON format </li>
<li>Line 13 remove all timestamps in the observer</li>
</ul>
<p>You can visualize the timeline data in a Chrome browser:</p>
<ul>
<li>Step 1: save the JSON timeline data to a file </li>
<li>Step 2: launch the Chrome browser and open a tab with the url: chrome://tracing </li>
<p>Tasks will be categorized by the executing thread and each task is named with <em>i_j</em> where <em>i</em> is the thread id and <em>j</em> is the task number. You can pan or zoom in/out the timeline to get a detailed view.</p>
<p>You can derive your own observer from the base interface class <aclass="el" href="classtf_1_1ExecutorObserverInterface.html" title="The interface class for creating an executor observer. ">tf::ExecutorObserverInterface</a> to customize the observing methods. </p>
</div></div><!-- contents -->
</div><!-- doc-content -->
<!-- start footer part -->
<divid="nav-path" class="navpath"><!-- id is needed for treeview function! -->