3#include "../observer/tfprof.hpp"
5#include "async_task.hpp"
64 friend class FlowBuilder;
67 friend class NonpreemptiveRuntime;
68 friend class Algorithm;
69 friend class TaskGroup;
92 size_t N = std::thread::hardware_concurrency(),
93 std::shared_ptr<WorkerInterface> wif =
nullptr
379 template<
typename P,
typename C>
410 template<
typename P,
typename C>
453 template <
typename T>
484 template <
typename P>
600 template <typename Observer, typename... ArgsT>
608 template <typename Observer>
671 template <typename F>
723 template <typename F>
827 template <typename F, std::input_iterator I>
996 template <typename F, std::input_iterator I>
1104 std::vector<Worker> _workers;
1105 std::vector<Buffer> _buffers;
1110 alignas(TF_CACHELINE_SIZE) std::atomic<size_t> _num_topologies {0};
1112 std::unordered_map<std::thread::id, Worker*> _t2w;
1113 std::unordered_set<std::shared_ptr<ObserverInterface>> _observers;
1116 void _observer_prologue(Worker&, Node*);
1117 void _observer_epilogue(Worker&, Node*);
1118 void _spawn(
size_t, std::shared_ptr<WorkerInterface>);
1119 void _exploit_task(Worker&, Node*&);
1120 bool _explore_task(Worker&, Node*&);
1121 void _schedule(Worker&, Node*);
1122 void _schedule(Node*);
1123 void _schedule_graph(Worker&, Graph&, Topology*, NodeBase*);
1125 void _set_up_topology(Worker*, Topology*);
1126 void _tear_down_topology(Worker&, Topology*, Node*&);
1127 void _tear_down_async(Worker&, Node*, Node*&);
1128 void _tear_down_dependent_async(Worker&, Node*, Node*&);
1129 void _tear_down_nonasync(Worker&, Node*, Node*&);
1130 void _tear_down_invoke(Worker&, Node*, Node*&);
1131 void _increment_topology();
1132 void _decrement_topology();
1133 void _invoke(Worker&, Node*);
1134 void _invoke_static_task(Worker&, Node*);
1135 void _invoke_nonpreemptive_runtime_task(Worker&, Node*);
1136 void _invoke_condition_task(Worker&, Node*, SmallVector<int>&);
1137 void _invoke_multi_condition_task(Worker&, Node*, SmallVector<int>&);
1138 void _process_dependent_async(Node*,
tf::AsyncTask&,
size_t&);
1139 void _process_exception(Worker&, Node*);
1140 void _update_cache(Worker&, Node*&, Node*);
1141 void _corun_graph(Worker&, Graph&, Topology*, NodeBase*);
1143 bool _wait_for_task(Worker&, Node*&);
1144 bool _invoke_subflow_task(Worker&, Node*);
1145 bool _invoke_module_task(Worker&, Node*);
1146 bool _invoke_adopted_module_task(Worker&, Node*);
1147 bool _invoke_module_task_impl(Worker&, Node*, Graph&);
1148 bool _invoke_async_task(Worker&, Node*);
1149 bool _invoke_dependent_async_task(Worker&, Node*);
1150 bool _invoke_runtime_task(Worker&, Node*);
1151 bool _invoke_runtime_task_impl(Worker&, Node*, std::function<
void(Runtime&)>&);
1152 bool _invoke_runtime_task_impl(Worker&, Node*, std::function<
void(Runtime&,
bool)>&);
1154 size_t _set_up_graph(Graph&, Topology*, NodeBase*);
1156 template <
typename P>
1157 void _corun_until(Worker&, P&&);
1159 template <std::input_iterator I>
1160 void _bulk_schedule(Worker&, I,
size_t);
1162 template <std::input_iterator I>
1163 void _bulk_schedule(I,
size_t);
1165 template <std::input_iterator I>
1166 void _bulk_spill(I,
size_t);
1168 template <std::input_iterator I>
1169 void _bulk_spill_round_robin(I,
size_t);
1172 void _bulk_update_cache(Worker&, Node*&, Node*, std::array<Node*, N>&,
size_t&);
1174 template <TaskParamsLike P,
typename F>
1175 auto _async(P&&, F&&, Topology*, NodeBase*);
1177 template <TaskParamsLike P,
typename F>
1178 void _silent_async(P&&, F&&, Topology*, NodeBase*);
1180 template <TaskParamsLike P,
typename F, std::input_iterator I>
1181 auto _dependent_async(P&&, F&&, I, I, Topology*, NodeBase*);
1183 template <TaskParamsLike P,
typename F, std::input_iterator I>
1184 auto _silent_dependent_async(P&&, F&&, I, I, Topology*, NodeBase*);
1186 template <
typename... ArgsT>
1187 void _schedule_async_task(ArgsT&&...);
1189 template <std::input_iterator I,
typename... ArgsT>
1190 AsyncTask _schedule_dependent_async_task(I, I,
size_t, ArgsT&&...);
1193#ifndef DOXYGEN_GENERATING_OUTPUT
1198 _buffers (std::bit_width(N)),
1202 TF_THROW(
"executor must define at least one worker");
1207#ifndef TF_DISABLE_EXCEPTION_HANDLING
1210 _spawn(N, std::move(wif));
1211#ifndef TF_DISABLE_EXCEPTION_HANDLING
1215 std::rethrow_exception(std::current_exception());
1220 if(
has_env(TF_ENABLE_PROFILER)) {
1231inline void Executor::_shutdown() {
1237 for(
size_t i=0; i<_workers.size(); ++i) {
1238 _workers[i]._done.test_and_set(std::memory_order_relaxed);
1241 _notifier.notify_all();
1245 for(
auto& w : _workers) {
1246 if(w._thread.joinable()) {
1254 return _workers.size();
1259 return _notifier.num_waiters();
1264 return _workers.size() + _buffers.size();
1269 return _num_topologies.load(std::memory_order_relaxed);
1274 auto itr = _t2w.find(std::this_thread::get_id());
1275 return itr == _t2w.end() ? nullptr : itr->second;
1280 auto i = _t2w.find(std::this_thread::get_id());
1281 return i == _t2w.end() ? -1 :
static_cast<int>(i->second->_id);
1285inline void Executor::_spawn(
size_t N, std::shared_ptr<WorkerInterface> wif) {
1287 for(
size_t id=0;
id<N; ++id) {
1288 _workers[id]._thread = std::thread([&,
id, wif] () {
1290 auto& worker = _workers[id];
1293 worker._sticky_victim = id;
1294 worker._rdgen.seed(
static_cast<uint32_t
>(std::hash<std::thread::id>()(std::this_thread::get_id())));
1298 wif->scheduler_prologue(worker);
1302 std::exception_ptr ptr =
nullptr;
1307#ifndef TF_DISABLE_EXCEPTION_HANDLING
1314 _exploit_task(worker, t);
1317 if(_wait_for_task(worker, t) ==
false) {
1322#ifndef TF_DISABLE_EXCEPTION_HANDLING
1325 ptr = std::current_exception();
1331 wif->scheduler_epilogue(worker, ptr);
1356 _t2w.emplace(_workers[
id]._thread.get_id(), &_workers[
id]);
1361inline bool Executor::_explore_task(
Worker& w, Node*& t) {
1369 if(_num_topologies.load(std::memory_order_relaxed) == 0) {
1374 const size_t MAX_STEALS = ((MAX_VICTIM + 1) << 1);
1378 size_t num_steals = 0;
1379 size_t vtm = w._sticky_victim;
1383 t = (vtm < _workers.size())
1384 ? _workers[vtm]._wsq.steal()
1385 : _buffers[vtm - _workers.size()].queue.steal();
1388 w._sticky_victim = vtm;
1394 vtm = w._rdgen() % (MAX_VICTIM - 1);
1395 if(vtm >= w._id) vtm++;
1397 if(++num_steals > MAX_STEALS) {
1398 std::this_thread::yield();
1399 if(num_steals > 150 + MAX_STEALS) {
1404 if(w._done.test(std::memory_order_relaxed)) {
1468inline void Executor::_exploit_task(
Worker& w, Node*& t) {
1476inline bool Executor::_wait_for_task(
Worker& w, Node*& t) {
1480 if(_explore_task(w, t) ==
false) {
1490 _notifier.prepare_wait(w._id);
1497 if(_num_topologies.load(std::memory_order_relaxed) == 0) {
1499 if(w._done.test(std::memory_order_relaxed)) {
1500 _notifier.cancel_wait(w._id);
1503 _notifier.commit_wait(w._id);
1508 for(
size_t b=0; b<_buffers.size(); ++b) {
1509 if(!_buffers[b].queue.empty()) {
1510 _notifier.cancel_wait(w._id);
1511 w._sticky_victim = b + _workers.size();
1521 for(
size_t k=0; k<_workers.size()-1; ++k) {
1522 if(
size_t vtm = k + (k >= w._id); !_workers[vtm]._wsq.empty()) {
1523 _notifier.cancel_wait(w._id);
1524 w._sticky_victim = vtm;
1530 if(w._done.test(std::memory_order_relaxed)) {
1531 _notifier.cancel_wait(w._id);
1536 _notifier.commit_wait(w._id);
1541template<
typename Observer,
typename... ArgsT>
1545 std::is_base_of_v<ObserverInterface, Observer>,
1546 "Observer must be derived from ObserverInterface"
1550 auto ptr = std::make_shared<Observer>(std::forward<ArgsT>(args)...);
1552 ptr->set_up(_workers.size());
1554 _observers.emplace(std::static_pointer_cast<ObserverInterface>(ptr));
1560template <
typename Observer>
1564 std::is_base_of_v<ObserverInterface, Observer>,
1565 "Observer must be derived from ObserverInterface"
1568 _observers.erase(std::static_pointer_cast<ObserverInterface>(ptr));
1573 return _observers.size();
1577inline void Executor::_spill(Node* item) {
1580 auto b = (
reinterpret_cast<uintptr_t
>(item) >> 16) % _buffers.size();
1581 std::scoped_lock lock(_buffers[b].mutex);
1582 _buffers[b].queue.push(item);
1589template <std::input_iterator I>
1590void Executor::_bulk_spill(I first,
size_t N) {
1592 auto b = ((
reinterpret_cast<uintptr_t
>(*first) * 2654435761ULL) >> 32) % _buffers.size();
1593 std::scoped_lock lock(_buffers[b].mutex);
1594 _buffers[b].queue.bulk_push(first, N);
1602template <std::input_iterator I>
1603void Executor::_bulk_spill_round_robin(I first,
size_t N) {
1606 const size_t B = _buffers.size();
1607 const size_t start = ((
reinterpret_cast<uintptr_t
>(*first) * 2654435761ULL) >> 32) % B;
1608 const size_t per_buf = (N + B - 1) / B;
1609 size_t remaining = N;
1610 for(
size_t i = 0; i < B && remaining > 0; ++i) {
1611 size_t b = (start + i) % B;
1612 size_t chunk = std::min(per_buf, remaining);
1614 std::scoped_lock lock(_buffers[b].mutex);
1615 _buffers[b].queue.bulk_push(first, chunk);
1623inline void Executor::_schedule(
Worker& worker, Node* node) {
1626 if(worker._wsq.try_push(node) ==
false) {
1629 _notifier.notify_one();
1633inline void Executor::_schedule(Node* node) {
1635 _notifier.notify_one();
1639template <std::input_iterator I>
1640void Executor::_bulk_schedule(
Worker& worker, I first,
size_t num_nodes) {
1642 if(num_nodes == 0) {
1651 if(
auto n = worker._wsq.try_bulk_push(first, num_nodes); n != num_nodes) {
1652 _bulk_spill(first, num_nodes - n);
1654 _notifier.notify_n(num_nodes);
1664template <std::input_iterator I>
1665inline void Executor::_bulk_schedule(I first,
size_t num_nodes) {
1667 if(num_nodes == 0) {
1676 _bulk_spill(first, num_nodes);
1677 _notifier.notify_n(num_nodes);
1681TF_FORCE_INLINE
void Executor::_update_cache(
Worker& worker, Node*& cache, Node* node) {
1682#ifdef TF_ENABLE_TASK_PRIORITY
1684 if(node->priority() < cache->priority()) {
1685 std::swap(cache, node);
1687 _schedule(worker, node);
1693 _schedule(worker, cache);
1701TF_FORCE_INLINE
void Executor::_bulk_update_cache(
1702 Worker& worker, Node*& cache, Node* node, std::array<Node*, N>& array,
size_t& n
1708 _bulk_schedule(worker, array, n);
1716inline void Executor::_invoke(
Worker& worker, Node* node) {
1718 #define TF_INVOKE_CONTINUATION() \
1721 goto begin_invoke; \
1726 Node* cache {
nullptr};
1729 if(node->_nstate & NSTATE::PREEMPTED) {
1736 if(node->_is_parent_cancelled()) {
1737 _tear_down_invoke(worker, node, cache);
1738 TF_INVOKE_CONTINUATION();
1743 if(node->_semaphores && !node->_semaphores->to_acquire.empty()) {
1744 SmallVector<Node*> waiters;
1745 if(!node->_acquire_all(waiters)) {
1746 _bulk_schedule(worker, waiters.begin(), waiters.size());
1753 SmallVector<int> conds;
1756 switch(node->_handle.index()) {
1759 _invoke_static_task(worker, node);
1764 case Node::RUNTIME:{
1765 if(_invoke_runtime_task(worker, node)) {
1772 case Node::NONPREEMPTIVE_RUNTIME:{
1773 _invoke_nonpreemptive_runtime_task(worker, node);
1778 case Node::SUBFLOW: {
1779 if(_invoke_subflow_task(worker, node)) {
1786 case Node::CONDITION: {
1787 _invoke_condition_task(worker, node, conds);
1792 case Node::MULTI_CONDITION: {
1793 _invoke_multi_condition_task(worker, node, conds);
1798 case Node::MODULE: {
1799 if(_invoke_module_task(worker, node)) {
1806 case Node::ADOPTED_MODULE: {
1807 if(_invoke_adopted_module_task(worker, node)) {
1815 if(_invoke_async_task(worker, node)) {
1818 _tear_down_async(worker, node, cache);
1819 TF_INVOKE_CONTINUATION();
1825 case Node::DEPENDENT_ASYNC: {
1826 if(_invoke_dependent_async_task(worker, node)) {
1829 _tear_down_dependent_async(worker, node, cache);
1830 TF_INVOKE_CONTINUATION();
1841 if(node->_semaphores && !node->_semaphores->to_release.empty()) {
1842 SmallVector<Node*> waiters;
1843 node->_release_all(waiters);
1844 _bulk_schedule(worker, waiters.begin(), waiters.size());
1853 node->_join_counter.fetch_add(
1854 node->_nstate & NSTATE::STRONG_DEPENDENCIES_MASK, std::memory_order_relaxed
1858 switch(node->_handle.index()) {
1861 case Node::CONDITION:
1862 case Node::MULTI_CONDITION: {
1863 for(
auto cond : conds) {
1864 if(cond >= 0 &&
static_cast<size_t>(cond) < node->_num_successors) {
1865 auto s = node->_edges[cond];
1867 s->_join_counter.store(0, std::memory_order_relaxed);
1868 node->_parent->_join_counter.fetch_add(1, std::memory_order_relaxed);
1869 _update_cache(worker, cache, s);
1877 for(
size_t i=0; i<node->_num_successors; ++i) {
1878 if(
auto s = node->_edges[i]; s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
1879 node->_parent->_join_counter.fetch_add(1, std::memory_order_relaxed);
1880 _update_cache(worker, cache, s);
1888 _tear_down_nonasync(worker, node, cache);
1889 TF_INVOKE_CONTINUATION();
1893inline void Executor::_tear_down_nonasync(
Worker& worker, Node* node, Node*& cache) {
1897 if(
auto parent = node->_parent; parent == node->_topology) {
1898 if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
1899 _tear_down_topology(worker, node->_topology, cache);
1905 auto state = parent->_nstate;
1906 if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
1908 if(state & NSTATE::PREEMPTED) {
1909 _update_cache(worker, cache,
static_cast<Node*
>(parent));
1916inline void Executor::_tear_down_invoke(
Worker& worker, Node* node, Node*& cache) {
1917 switch(node->_handle.index()) {
1919 _tear_down_async(worker, node, cache);
1922 case Node::DEPENDENT_ASYNC:
1923 _tear_down_dependent_async(worker, node, cache);
1927 _tear_down_nonasync(worker, node, cache);
1933inline void Executor::_observer_prologue(
Worker& worker, Node* node) {
1934 for(
auto& observer : _observers) {
1935 observer->on_entry(WorkerView(worker), TaskView(*node));
1940inline void Executor::_observer_epilogue(
Worker& worker, Node* node) {
1941 for(
auto& observer : _observers) {
1942 observer->on_exit(WorkerView(worker), TaskView(*node));
1947inline void Executor::_process_exception(
Worker&, Node* node) {
1952 NodeBase* ea = node;
1953 NodeBase* ia =
nullptr;
1955 while(ea && (ea->_estate.load(std::memory_order_relaxed) & ESTATE::EXPLICITLY_ANCHORED) == 0) {
1956 ea->_estate.fetch_or(ESTATE::EXCEPTION, std::memory_order_relaxed);
1958 if(ia ==
nullptr && (ea->_nstate & NSTATE::IMPLICITLY_ANCHORED)) {
1965 constexpr static auto flag = ESTATE::EXCEPTION | ESTATE::CAUGHT;
1970 if((ea->_estate.fetch_or(flag, std::memory_order_relaxed) & ESTATE::CAUGHT) == 0) {
1971 ea->_exception_ptr = std::current_exception();
1977 if((ia->_estate.fetch_or(flag, std::memory_order_relaxed) & ESTATE::CAUGHT) == 0) {
1978 ia->_exception_ptr = std::current_exception();
1986 node->_exception_ptr = std::current_exception();
1990inline void Executor::_invoke_static_task(
Worker& worker, Node* node) {
1991 _observer_prologue(worker, node);
1992 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
1993 std::get_if<Node::Static>(&node->_handle)->work();
1995 _observer_epilogue(worker, node);
1999inline bool Executor::_invoke_subflow_task(
Worker& worker, Node* node) {
2001 auto& h = *std::get_if<Node::Subflow>(&node->_handle);
2002 auto& g = h.subgraph;
2004 if((node->_nstate & NSTATE::PREEMPTED) == 0) {
2007 Subflow sf(*
this, worker, node, g);
2010 _observer_prologue(worker, node);
2011 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
2014 _observer_epilogue(worker, node);
2018 if(sf.joinable() && !g.empty()) {
2021 node->_nstate |= NSTATE::PREEMPTED;
2024 _schedule_graph(worker, g, node->_topology, node);
2029 node->_nstate &= ~NSTATE::PREEMPTED;
2036 if((node->_nstate & NSTATE::RETAIN_SUBFLOW) == 0) {
2044inline void Executor::_invoke_condition_task(
2047 _observer_prologue(worker, node);
2048 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
2049 auto& work = std::get_if<Node::Condition>(&node->_handle)->work;
2052 _observer_epilogue(worker, node);
2056inline void Executor::_invoke_multi_condition_task(
2059 _observer_prologue(worker, node);
2060 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
2061 conds = std::get_if<Node::MultiCondition>(&node->_handle)->work();
2063 _observer_epilogue(worker, node);
2067inline bool Executor::_invoke_module_task(
Worker& w, Node* node) {
2068 return _invoke_module_task_impl(w, node, std::get_if<Node::Module>(&node->_handle)->graph);
2072inline bool Executor::_invoke_adopted_module_task(
Worker& w, Node* node) {
2073 return _invoke_module_task_impl(w, node, std::get_if<Node::AdoptedModule>(&node->_handle)->graph);
2077inline bool Executor::_invoke_module_task_impl(
Worker& w, Node* node,
Graph& graph) {
2085 if((node->_nstate & NSTATE::PREEMPTED) == 0) {
2087 node->_nstate |= NSTATE::PREEMPTED;
2088 _schedule_graph(w, graph, node->_topology, node);
2093 node->_nstate &= ~NSTATE::PREEMPTED;
2100inline bool Executor::_invoke_async_task(
Worker& worker, Node* node) {
2101 auto& work = std::get_if<Node::Async>(&node->_handle)->work;
2102 switch(work.index()) {
2105 _observer_prologue(worker, node);
2106 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
2107 std::get_if<0>(&work)->operator()();
2109 _observer_epilogue(worker, node);
2114 if(_invoke_runtime_task_impl(worker, node, *std::get_if<1>(&work))) {
2121 if(_invoke_runtime_task_impl(worker, node, *std::get_if<2>(&work))) {
2131inline bool Executor::_invoke_dependent_async_task(
Worker& worker, Node* node) {
2132 auto& work = std::get_if<Node::DependentAsync>(&node->_handle)->work;
2133 switch(work.index()) {
2136 _observer_prologue(worker, node);
2137 TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
2138 std::get_if<0>(&work)->operator()();
2140 _observer_epilogue(worker, node);
2145 if(_invoke_runtime_task_impl(worker, node, *std::get_if<1>(&work))) {
2152 if(_invoke_runtime_task_impl(worker, node, *std::get_if<2>(&work))) {
2162 return run_n(f, 1, [](){});
2167 return run_n(std::move(f), 1, [](){});
2171template <
typename C>
2173 return run_n(f, 1, std::forward<C>(c));
2177template <
typename C>
2179 return run_n(std::move(f), 1, std::forward<C>(c));
2184 return run_n(f, repeat, [](){});
2189 return run_n(std::move(f), repeat, [](){});
2193template <
typename C>
2196 f, [repeat]()
mutable {
return repeat-- == 0; }, std::forward<C>(c)
2201template <
typename C>
2204 std::move(f), [repeat]()
mutable {
return repeat-- == 0; }, std::forward<C>(c)
2211 return run_until(f, std::forward<P>(pred), [](){});
2217 return run_until(std::move(f), std::forward<P>(pred), [](){});
2221template <
typename P,
typename C>
2225 if(f.empty() || p()) {
2227 std::promise<void> promise;
2228 promise.set_value();
2229 return tf::Future<void>(promise.get_future());
2232 _increment_topology();
2235 auto t = std::make_shared<Topology>(f, std::forward<P>(p), std::forward<C>(c));
2239 tf::Future<void> future(t->_promise.get_future(), t);
2242 if(f._fetch_enqueue(t) == 0) {
2252template <
typename P>
2257 TF_THROW(
"corun_until must be called by a worker of the executor");
2260 _corun_until(*w, std::forward<P>(predicate));
2317template <
typename P>
2318void Executor::_corun_until(
Worker& w, P&& stop_predicate) {
2321 const size_t MAX_STEALS = ((MAX_VICTIM + 1) << 1);
2325 while(!stop_predicate()) {
2329 if(
auto t = w._wsq.pop(); t) {
2333 size_t num_steals = 0;
2334 size_t vtm = w._sticky_victim;
2338 t = (vtm < _workers.size())
2339 ? _workers[vtm]._wsq.steal()
2340 : _buffers[vtm-_workers.size()].queue.steal();
2344 w._sticky_victim = vtm;
2347 else if(!stop_predicate()) {
2348 if(++num_steals > MAX_STEALS) {
2349 std::this_thread::yield();
2351 vtm = w._rdgen() % MAX_VICTIM;
2362template <
typename T>
2367 TF_THROW(
"corun must be called by a worker of the executor");
2375inline void Executor::_corun_graph(
Worker& w,
Graph& g, Topology* tpg, NodeBase* p) {
2384 ExplicitAnchorGuard anchor(p);
2385 _schedule_graph(w, g, tpg, p);
2386 _corun_until(w, [p] () ->
bool {
2387 return p->_join_counter.load(std::memory_order_acquire) == 0; }
2392 p->_rethrow_exception();
2396inline void Executor::_increment_topology() {
2397 _num_topologies.fetch_add(1, std::memory_order_relaxed);
2401inline void Executor::_decrement_topology() {
2402 if(_num_topologies.fetch_sub(1, std::memory_order_acq_rel) == 1) {
2403 _num_topologies.notify_all();
2409 size_t n = _num_topologies.load(std::memory_order_acquire);
2411 _num_topologies.wait(n, std::memory_order_acquire);
2412 n = _num_topologies.load(std::memory_order_acquire);
2417inline void Executor::_schedule_graph(
2418 Worker& worker,
Graph& graph, Topology* tpg, NodeBase* parent
2420 size_t num_srcs = _set_up_graph(graph, tpg, parent);
2421 parent->_join_counter.fetch_add(num_srcs, std::memory_order_relaxed);
2422 _bulk_schedule(worker, graph.begin(), num_srcs);
2426inline void Executor::_set_up_topology(
Worker* w, Topology* tpg) {
2428 auto& g = tpg->_taskflow._graph;
2429 size_t num_srcs = _set_up_graph(g, tpg, tpg);
2430 tpg->_join_counter.store(num_srcs, std::memory_order_relaxed);
2431 w ? _bulk_schedule(*w, g.begin(), num_srcs) : _bulk_schedule(g.begin(), num_srcs);
2435inline size_t Executor::_set_up_graph(
Graph& graph, Topology* tpg, NodeBase* parent) {
2437 auto first = graph.begin();
2438 auto last = graph.end();
2440 for(; first != last; ++first) {
2443 node->_topology = tpg;
2444 node->_parent = parent;
2445#ifdef TF_ENABLE_TASK_PRIORITY
2446 node->_nstate &= NSTATE::PRIORITY_MASK;
2448 node->_nstate &= NSTATE::NONE;
2450 node->_estate.store(ESTATE::NONE, std::memory_order_relaxed);
2451 node->_set_up_join_counter();
2452 node->_exception_ptr =
nullptr;
2456 if(node->num_predecessors() == 0) {
2457 std::iter_swap(send++, first);
2460 return send - graph.begin();
2464inline void Executor::_tear_down_topology(
Worker& worker, Topology* tpg, Node*& cache) {
2466 auto &f = tpg->_taskflow;
2472 if(!tpg->cancelled() && !tpg->_predicate()) {
2475 _schedule_graph(worker, tpg->_taskflow._graph, tpg, tpg);
2484 if(std::unique_lock<std::mutex> lock(f._mutex); f._topologies.size()>1) {
2486 auto fetched_tpg {std::move(f._topologies.front())};
2489 f._topologies.pop();
2490 tpg = f._topologies.front().get();
2496 fetched_tpg->_carry_out_promise();
2499 _decrement_topology();
2501 _schedule_graph(worker, tpg->_taskflow._graph, tpg, tpg);
2506 auto fetched_tpg {std::move(f._topologies.front())};
2509 f._topologies.pop();
2515 fetched_tpg->_carry_out_promise();
2517 _decrement_topology();
2520 if(
auto parent = fetched_tpg->_parent; parent) {
2522 if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
2525 _update_cache(worker, cache,
static_cast<Node*
>(parent));
2540 TF_THROW(
"subflow already joined");
2543 _executor._corun_graph(_worker, _graph, _node->_topology, _node);
2546 _node->_nstate |= NSTATE::JOINED_SUBFLOW;
class to hold a dependent asynchronous task with shared ownership
Definition async_task.hpp:45
auto dependent_async(F &&func, Tasks &&... tasks)
runs the given function asynchronously when the given predecessors finish
void silent_async(P &¶ms, F &&func)
similar to tf::Executor::async but does not return a future object
tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)
runs the given function asynchronously when the given predecessors finish
tf::Future< void > run_until(Taskflow &taskflow, P &&pred)
runs a taskflow multiple times until the predicate becomes true
void corun_until(P &&predicate)
keeps running the work-stealing loop until the predicate returns true
void remove_observer(std::shared_ptr< Observer > observer)
removes an observer from the executor
tf::Future< void > run(Taskflow &&taskflow)
runs a moved taskflow once
tf::Future< void > run(Taskflow &taskflow)
runs a taskflow once
size_t num_waiters() const noexcept
queries the number of workers that are in the waiting loop
tf::Future< void > run(Taskflow &&taskflow, C &&callable)
runs a moved taskflow once and invoke a callback upon completion
~Executor()
destructs the executor
int this_worker_id() const
queries the id of the caller thread within this executor
size_t num_queues() const noexcept
queries the number of work-stealing queues used by the executor
tf::Future< void > run_n(Taskflow &taskflow, size_t N)
runs a taskflow for N times
size_t num_topologies() const
queries the number of running topologies at the time of this call
Executor(size_t N=std::thread::hardware_concurrency(), std::shared_ptr< WorkerInterface > wif=nullptr)
constructs the executor with N worker threads
TaskGroup task_group()
creates a task group that executes a collection of asynchronous tasks
Definition task_group.hpp:863
void corun(T &target)
runs a target graph and waits until it completes using an internal worker of this executor
size_t num_workers() const noexcept
queries the number of worker threads
tf::Future< void > run_until(Taskflow &&taskflow, P &&pred)
runs a moved taskflow and keeps running it until the predicate becomes true
void wait_for_all()
waits for all tasks to complete
tf::Future< void > run_n(Taskflow &taskflow, size_t N, C &&callable)
runs a taskflow for N times and then invokes a callback
tf::Future< void > run(Taskflow &taskflow, C &&callable)
runs a taskflow once and invoke a callback upon completion
tf::Future< void > run_n(Taskflow &&taskflow, size_t N)
runs a moved taskflow for N times
tf::Future< void > run_n(Taskflow &&taskflow, size_t N, C &&callable)
runs a moved taskflow for N times and then invokes a callback
tf::Future< void > run_until(Taskflow &taskflow, P &&pred, C &&callable)
runs a taskflow multiple times until the predicate becomes true and then invokes the callback
Worker * this_worker()
queries pointer to the calling worker if it belongs to this executor, otherwise returns nullptr
auto async(P &¶ms, F &&func)
creates a parameterized asynchronous task to run the given function
std::shared_ptr< Observer > make_observer(ArgsT &&... args)
constructs an observer to inspect the activities of worker threads
size_t num_observers() const noexcept
queries the number of observers
class to access the result of an execution
Definition taskflow.hpp:640
class to create a graph object
Definition graph.hpp:47
class to define a vector optimized for small array
Definition small_vector.hpp:931
void join()
enables the subflow to join its parent task
bool joinable() const noexcept
queries if the subflow is joinable
Definition flow_builder.hpp:2012
class to create a taskflow object
Definition taskflow.hpp:64
class to create a lock-free unbounded work-stealing queue
Definition wsq.hpp:105
class to create a worker in an executor
Definition worker.hpp:55
concept to check if a type is a tf::AsyncTask
Definition async_task.hpp:314
concept to check if a type is a task parameter
Definition graph.hpp:228
taskflow namespace
Definition small_vector.hpp:20
NonblockingNotifier DefaultNotifier
the default notifier type used by Taskflow
Definition worker.hpp:38
Graph & retrieve_graph(T &target)
retrieves a reference to the underlying tf::Graph from an object
Definition graph.hpp:1108
bool has_env(const std::string &str)
checks whether an environment variable is defined
Definition os.hpp:310