FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Cpp-Taskflow

Cpp-Taskflow
Cpp-Taskflow  2.3.0
executor.hpp
1 #pragma once
2 
3 #include <iostream>
4 #include <vector>
5 #include <cstdlib>
6 #include <cstdio>
7 #include <random>
8 #include <atomic>
9 #include <memory>
10 #include <deque>
11 #include <optional>
12 #include <thread>
13 #include <algorithm>
14 #include <set>
15 #include <numeric>
16 #include <cassert>
17 
18 #include "wsq.hpp"
19 #include "notifier.hpp"
20 #include "observer.hpp"
21 #include "taskflow.hpp"
22 
23 namespace tf {
24 
33 class Executor {
34 
35  struct Worker {
36  unsigned id;
37  std::mt19937 rdgen { std::random_device{}() };
39  std::optional<Node*> cache;
40 
41  int num_executed {0};
42  };
43 
44  struct PerThread {
45  Worker* worker {nullptr};
46  };
47 
48  public:
49 
53  explicit Executor(unsigned n = std::thread::hardware_concurrency());
54 
58  ~Executor();
59 
67  std::future<void> run(Taskflow& taskflow);
68 
77  template<typename C>
78  std::future<void> run(Taskflow& taskflow, C&& callable);
79 
88  std::future<void> run_n(Taskflow& taskflow, size_t N);
89 
99  template<typename C>
100  std::future<void> run_n(Taskflow& taskflow, size_t N, C&& callable);
101 
111  template<typename P>
112  std::future<void> run_until(Taskflow& taskflow, P&& pred);
113 
124  template<typename P, typename C>
125  std::future<void> run_until(Taskflow& taskflow, P&& pred, C&& callable);
126 
130  void wait_for_all();
131 
137  size_t num_workers() const;
138 
152  template<typename Observer, typename... Args>
153  Observer* make_observer(Args&&... args);
154 
158  void remove_observer();
159 
163  std::optional<unsigned> this_worker_id() const;
164 
165  private:
166 
167  std::condition_variable _topology_cv;
168  std::mutex _topology_mutex;
169  std::mutex _queue_mutex;
170 
171  unsigned _num_topologies {0};
172 
173  // scheduler field
174  std::vector<Worker> _workers;
176  std::vector<std::thread> _threads;
177 
179 
180  std::atomic<size_t> _num_actives {0};
181  std::atomic<size_t> _num_thieves {0};
182  std::atomic<bool> _done {0};
183 
184  Notifier _notifier;
185 
187 
188  unsigned _find_victim(unsigned);
189 
190  PerThread& _per_thread() const;
191 
192  bool _wait_for_task(Worker&, std::optional<Node*>&);
193 
194  void _spawn(unsigned);
195  void _exploit_task(Worker&, std::optional<Node*>&);
196  void _explore_task(Worker&, std::optional<Node*>&);
197  void _schedule(Node*, bool);
198  void _schedule(PassiveVector<Node*>&);
199  void _invoke(Worker&, Node*);
200  void _invoke_static_work(Worker&, Node*);
201  void _invoke_dynamic_work(Worker&, Node*, Subflow&);
202  void _set_up_module_node(Node*);
203  void _set_up_topology(Topology*);
204  void _tear_down_topology(Topology**);
205  void _increment_topology();
206  void _decrement_topology();
207  void _decrement_topology_and_notify();
208 };
209 
210 // Constructor
211 inline Executor::Executor(unsigned N) :
212  _workers {N},
213  _waiters {N},
214  _notifier {_waiters} {
215 
216  if(N == 0) {
217  TF_THROW(Error::EXECUTOR, "no workers to execute the graph");
218  }
219 
220  _spawn(N);
221 }
222 
223 // Destructor
225 
226  // wait for all topologies to complete
227  wait_for_all();
228 
229  // shut down the scheduler
230  _done = true;
231  _notifier.notify(true);
232 
233  for(auto& t : _threads){
234  t.join();
235  }
236 }
237 
238 // Function: num_workers
239 inline size_t Executor::num_workers() const {
240  return _workers.size();
241 }
242 
243 // Function: _per_thread
244 inline Executor::PerThread& Executor::_per_thread() const {
245  thread_local PerThread pt;
246  return pt;
247 }
248 
249 // Function: this_worker_id
250 inline std::optional<unsigned> Executor::this_worker_id() const {
251  if(auto worker = _per_thread().worker; worker) {
252  return worker->id;
253  }
254  else {
255  return std::nullopt;
256  }
257 }
258 
259 // Procedure: _spawn
260 inline void Executor::_spawn(unsigned N) {
261 
262  // Lock to synchronize all workers before creating _worker_maps
263  for(unsigned i=0; i<N; ++i) {
264 
265  _workers[i].id = i;
266 
267  _threads.emplace_back([this] (Worker& w) -> void {
268 
269  PerThread& pt = _per_thread();
270  pt.worker = &w;
271 
272  std::optional<Node*> t;
273 
274  // must use 1 as condition instead of !done
275  while(1) {
276 
277  // execute the tasks.
278  _exploit_task(w, t);
279 
280  // wait for tasks
281  if(_wait_for_task(w, t) == false) {
282  break;
283  }
284  }
285 
286  }, std::ref(_workers[i]));
287  }
288 }
289 
290 // Function: _find_victim
291 inline unsigned Executor::_find_victim(unsigned thief) {
292 
293  /*unsigned l = 0;
294  unsigned r = _workers.size() - 1;
295  unsigned vtm = std::uniform_int_distribution<unsigned>{l, r}(
296  _workers[thief].rdgen
297  );
298 
299  // try to look for a task from other workers
300  for(unsigned i=0; i<_workers.size(); ++i){
301 
302  if((thief == vtm && !_queue.empty()) ||
303  (thief != vtm && !_workers[vtm].queue.empty())) {
304  return vtm;
305  }
306 
307  if(++vtm; vtm == _workers.size()) {
308  vtm = 0;
309  }
310  } */
311 
312  // try to look for a task from other workers
313  for(unsigned vtm=0; vtm<_workers.size(); ++vtm){
314  if((thief == vtm && !_queue.empty()) ||
315  (thief != vtm && !_workers[vtm].queue.empty())) {
316  return vtm;
317  }
318  }
319 
320  return static_cast<unsigned>(_workers.size());
321 }
322 
323 // Function: _explore_task
324 inline void Executor::_explore_task(Worker& thief, std::optional<Node*>& t) {
325 
326  //assert(_workers[thief].queue.empty());
327  assert(!t);
328 
329  const unsigned l = 0;
330  const unsigned r = static_cast<unsigned>(_workers.size()) - 1;
331 
332  const size_t F = (_workers.size() + 1) << 1;
333  const size_t Y = 100;
334 
335  size_t f = 0;
336  size_t y = 0;
337 
338  // explore
339  while(!_done) {
340 
341  unsigned vtm = std::uniform_int_distribution<unsigned>{l, r}(thief.rdgen);
342 
343  t = (vtm == thief.id) ? _queue.steal() : _workers[vtm].queue.steal();
344 
345  if(t) {
346  break;
347  }
348 
349  if(f++ > F) {
350  if(std::this_thread::yield(); y++ > Y) {
351  break;
352  }
353  }
354 
355  /*if(auto vtm = _find_victim(thief); vtm != _workers.size()) {
356  t = (vtm == thief) ? _queue.steal() : _workers[vtm].queue.steal();
357  // successful thief
358  if(t) {
359  break;
360  }
361  }
362  else {
363  if(f++ > F) {
364  if(std::this_thread::yield(); y++ > Y) {
365  break;
366  }
367  }
368  }*/
369  }
370 
371 }
372 
373 // Procedure: _exploit_task
374 inline void Executor::_exploit_task(Worker& worker, std::optional<Node*>& t) {
375 
376  assert(!worker.cache);
377 
378  if(t) {
379  if(_num_actives.fetch_add(1) == 0 && _num_thieves == 0) {
380  _notifier.notify(false);
381  }
382 
383  Topology *tpg = (*t)->_topology;
384  Node* prev_parent {(*t)->_parent};
385  worker.num_executed = 1;
386 
387  do {
388  // Only joined subflow will enter block
389  // Flush the num_executed if encountering a different subflow
390  //if((*t)->_parent != prev_parent) {
391  // if(prev_parent == nullptr) {
392  // (*t)->_topology->_join_counter.fetch_sub(worker.num_executed);
393  // }
394  // else {
395  // auto ret = prev_parent->_join_counter.fetch_sub(worker.num_executed);
396  // if(ret == worker.num_executed) {
397  // _schedule(prev_parent, false);
398  // }
399  // }
400  // worker.num_executed = 1;
401  // prev_parent = (*t)->_parent;
402  //}
403 
404  _invoke(worker, *t);
405 
406  if(worker.cache) {
407  t = *worker.cache;
408  worker.cache = std::nullopt;
409  }
410  else {
411  t = worker.queue.pop();
412  if(t) {
413  // We only increment the counter when poping task from queue (NOT including cache!)
414  if((*t)->_parent == prev_parent) {
415  worker.num_executed ++;
416  }
417  // joined subflow
418  else {
419  if(prev_parent == nullptr) {
420  // still have tasks so the topology join counter can't be zero
421  (*t)->_topology->_join_counter.fetch_sub(worker.num_executed);
422  }
423  else {
424  auto ret = prev_parent->_join_counter.fetch_sub(worker.num_executed);
425  if(ret == worker.num_executed) {
426  //_schedule(prev_parent, false);
427  worker.queue.push(prev_parent);
428  }
429  }
430  worker.num_executed = 1;
431  prev_parent = (*t)->_parent;
432  }
433  }
434  else {
435  // If no more local tasks!
436  if(prev_parent == nullptr) {
437  if(tpg->_join_counter.fetch_sub(worker.num_executed) == worker.num_executed) {
438  // TODO: Store tpg in local variable not in worker
439  _tear_down_topology(&tpg);
440  if(tpg != nullptr) {
441  t = worker.queue.pop();
442  if(t) {
443  worker.num_executed = 1;
444  }
445  }
446  }
447  }
448  else {
449  if(prev_parent->_join_counter.fetch_sub(worker.num_executed) == worker.num_executed) {
450  t = prev_parent;
451  worker.num_executed = 1;
452  prev_parent = prev_parent->_parent;
453  }
454  }
455  }
456  }
457  } while(t);
458 
459  --_num_actives;
460  }
461 }
462 
463 // Function: _wait_for_task
464 inline bool Executor::_wait_for_task(Worker& worker, std::optional<Node*>& t) {
465 
466  wait_for_task:
467 
468  assert(!t);
469 
470  ++_num_thieves;
471 
472  explore_task:
473 
474  if(_explore_task(worker, t); t) {
475  if(auto N = _num_thieves.fetch_sub(1); N == 1) {
476  _notifier.notify(false);
477  }
478  return true;
479  }
480 
481  auto waiter = &_waiters[worker.id];
482 
483  _notifier.prepare_wait(waiter);
484 
485  //if(auto vtm = _find_victim(me); vtm != _workers.size()) {
486  if(!_queue.empty()) {
487 
488  _notifier.cancel_wait(waiter);
489  //t = (vtm == me) ? _queue.steal() : _workers[vtm].queue.steal();
490 
491  if(t = _queue.steal(); t) {
492  if(auto N = _num_thieves.fetch_sub(1); N == 1) {
493  _notifier.notify(false);
494  }
495  return true;
496  }
497  else {
498  goto explore_task;
499  }
500  }
501 
502  if(_done) {
503  _notifier.cancel_wait(waiter);
504  _notifier.notify(true);
505  --_num_thieves;
506  return false;
507  }
508 
509  if(_num_thieves.fetch_sub(1) == 1 && _num_actives) {
510  _notifier.cancel_wait(waiter);
511  goto wait_for_task;
512  }
513 
514  // Now I really need to relinguish my self to others
515  _notifier.commit_wait(waiter);
516 
517  return true;
518 }
519 
520 // Function: make_observer
521 template<typename Observer, typename... Args>
522 Observer* Executor::make_observer(Args&&... args) {
523  // use a local variable to mimic the constructor
524  auto tmp = std::make_unique<Observer>(std::forward<Args>(args)...);
525  tmp->set_up(_workers.size());
526  _observer = std::move(tmp);
527  return static_cast<Observer*>(_observer.get());
528 }
529 
530 // Procedure: remove_observer
532  _observer.reset();
533 }
534 
535 // Procedure: _schedule
536 // The main procedure to schedule a give task node.
537 // Each task node has two types of tasks - regular and subflow.
538 inline void Executor::_schedule(Node* node, bool bypass) {
539 
540  //assert(_workers.size() != 0);
541 
542  // module node need another initialization
543  if(node->_module && !node->_module->empty() && !node->_has_state(Node::SPAWNED)) {
544  _set_up_module_node(node);
545  }
546 
547  // caller is a worker to this pool
548  if(auto worker = _per_thread().worker; worker != nullptr) {
549  if(!bypass) {
550  worker->queue.push(node);
551  }
552  else {
553  assert(!worker->cache);
554  worker->cache = node;
555  }
556  return;
557  }
558 
559  // other threads
560  {
561  std::scoped_lock lock(_queue_mutex);
562  _queue.push(node);
563  }
564 
565  _notifier.notify(false);
566 }
567 
568 // Procedure: _schedule
569 // The main procedure to schedule a set of task nodes.
570 // Each task node has two types of tasks - regular and subflow.
571 inline void Executor::_schedule(PassiveVector<Node*>& nodes) {
572 
573  //assert(_workers.size() != 0);
574 
575  // We need to cacth the node count to avoid accessing the nodes
576  // vector while the parent topology is removed!
577  const auto num_nodes = nodes.size();
578 
579  if(num_nodes == 0) {
580  return;
581  }
582 
583  for(auto node : nodes) {
584  if(node->_module && !node->_module->empty() && !node->_has_state(Node::SPAWNED)) {
585  _set_up_module_node(node);
586  }
587  }
588 
589  // worker thread
590  if(auto worker = _per_thread().worker; worker != nullptr) {
591  for(size_t i=0; i<num_nodes; ++i) {
592  worker->queue.push(nodes[i]);
593  }
594  return;
595  }
596 
597  // other threads
598  {
599  std::scoped_lock lock(_queue_mutex);
600  for(size_t k=0; k<num_nodes; ++k) {
601  _queue.push(nodes[k]);
602  }
603  }
604 
605  if(num_nodes >= _workers.size()) {
606  _notifier.notify(true);
607  }
608  else {
609  for(size_t k=0; k<num_nodes; ++k) {
610  _notifier.notify(false);
611  }
612  }
613 }
614 
615 
616 // Procedure: _invoke
617 inline void Executor::_invoke(Worker& worker, Node* node) {
618 
619  //assert(_workers.size() != 0);
620 
621  // Here we need to fetch the num_successors first to avoid the invalid memory
622  // access caused by topology clear.
623  const auto num_successors = node->num_successors();
624 
625  // condition task
626  if(node->_work.index() == Node::CONDITION_WORK) {
627 
628  if(node->_has_state(Node::BRANCH)) {
629  node->_join_counter = node->num_strong_dependents();
630  }
631  else {
632  node->_join_counter = node->num_dependents();
633  }
634 
635  if(size_t id = std::get<Node::ConditionWork>(node->_work)(); id < num_successors) {
636  node->_successors[id]->_join_counter.store(0);
637  _schedule(node->_successors[id], true);
638  }
639  return ;
640  }
641  // static task
642  // The default node work type. We only need to execute the callback if any.
643  else if(auto index=node->_work.index(); index == Node::STATIC_WORK) {
644  if(node->_module != nullptr) {
645  bool first_time = !node->_has_state(Node::SPAWNED);
646  _invoke_static_work(worker, node);
647  if(first_time) {
648  return ;
649  }
650  }
651  else {
652  _invoke_static_work(worker, node);
653  }
654  }
655  // dynamic task
656  else if (index == Node::DYNAMIC_WORK){
657 
658  // Clear the subgraph before the task execution
659  if(!node->_has_state(Node::SPAWNED)) {
660  if(node->_subgraph) {
661  node->_subgraph->clear();
662  }
663  else {
664  node->_subgraph.emplace();
665  }
666  }
667 
668  Subflow fb(*(node->_subgraph));
669 
670  _invoke_dynamic_work(worker, node, fb);
671 
672  // Need to create a subflow if first time & subgraph is not empty
673  if(!node->_has_state(Node::SPAWNED)) {
674  node->_set_state(Node::SPAWNED);
675  if(!node->_subgraph->empty()) {
676  // For storing the source nodes
677  PassiveVector<Node*> src;
678 
679  for(auto& n: node->_subgraph->nodes()) {
680 
681  n->_topology = node->_topology;
682  n->_set_up_join_counter();
683 
684  //n->_set_state(Node::SUBTASK);
685 
686  if(!fb.detached()) {
687  n->_parent = node;
688  }
689 
690  if(n->num_dependents() == 0) {
691  src.push_back(n.get());
692  }
693  }
694 
695  const bool join = fb.joined();
696  if(!join) {
697  // Detach mode
698  node->_topology->_join_counter.fetch_add(src.size());
699  }
700  else {
701  // Join mode
702  node->_join_counter.fetch_add(src.size());
703 
704  // spawned node needs another second-round execution
705  if(node->_parent == nullptr) {
706  node->_topology->_join_counter.fetch_add(1);
707  }
708  else {
709  node->_parent->_join_counter.fetch_add(1);
710  }
711  }
712 
713  _schedule(src);
714 
715  if(join) {
716  return;
717  }
718  } // End of first time
719  }
720  } // End of DynamicWork -----------------------------------------------------
721 
722 
723  // We MUST recover the dependency since subflow is a condition node can go back (cyclic)
724  // This must be done before scheduling the successors, otherwise this might cause
725  // race condition on the _dependents
726  if(node->_has_state(Node::BRANCH)) {
727  // If this is a case node, we need to deduct condition predecessors
728  node->_join_counter = node->num_strong_dependents();
729  }
730  else {
731  node->_join_counter = node->num_dependents();
732  }
733 
734  node->_unset_state(Node::SPAWNED);
735 
736  // At this point, the node storage might be destructed.
737  Node* cache {nullptr};
738  size_t num_spawns {0};
739 
740  auto& c = (node->_parent) ? node->_parent->_join_counter : node->_topology->_join_counter;
741 
742  for(size_t i=0; i<num_successors; ++i) {
743  if(--(node->_successors[i]->_join_counter) == 0) {
744  if(cache) {
745  if(num_spawns == 0) {
746  c.fetch_add(num_successors);
747  }
748  num_spawns++;
749  _schedule(cache, false);
750  }
751  cache = node->_successors[i];
752  }
753  }
754 
755  if(num_spawns) {
756  worker.num_executed += (node->_successors.size() - num_spawns);
757  }
758 
759  if(cache) {
760  _schedule(cache, true);
761  }
762 }
763 
764 // Procedure: _invoke_static_work
765 inline void Executor::_invoke_static_work(Worker& worker, Node* node) {
766  if(_observer) {
767  _observer->on_entry(worker.id, TaskView(node));
768  std::invoke(std::get<Node::StaticWork>(node->_work));
769  _observer->on_exit(worker.id, TaskView(node));
770  }
771  else {
772  std::invoke(std::get<Node::StaticWork>(node->_work));
773  }
774 }
775 
776 // Procedure: _invoke_dynamic_work
777 inline void Executor::_invoke_dynamic_work(Worker& worker, Node* node, Subflow& sf) {
778  if(_observer) {
779  _observer->on_entry(worker.id, TaskView(node));
780  std::invoke(std::get<Node::DynamicWork>(node->_work), sf);
781  _observer->on_exit(worker.id, TaskView(node));
782  }
783  else {
784  std::invoke(std::get<Node::DynamicWork>(node->_work), sf);
785  }
786 }
787 
788 // Function: run
790  return run_n(f, 1, [](){});
791 }
792 
793 // Function: run
794 template <typename C>
796  static_assert(std::is_invocable<C>::value);
797  return run_n(f, 1, std::forward<C>(c));
798 }
799 
800 // Function: run_n
801 inline std::future<void> Executor::run_n(Taskflow& f, size_t repeat) {
802  return run_n(f, repeat, [](){});
803 }
804 
805 // Function: run_n
806 template <typename C>
807 std::future<void> Executor::run_n(Taskflow& f, size_t repeat, C&& c) {
808  return run_until(f, [repeat]() mutable { return repeat-- == 0; }, std::forward<C>(c));
809 }
810 
811 // Function: run_until
812 template<typename P>
814  return run_until(f, std::forward<P>(pred), [](){});
815 }
816 
817 // Function: _set_up_topology
818 inline void Executor::_set_up_topology(Topology* tpg) {
819 
820  tpg->_sources.clear();
821 
822  // scan each node in the graph and build up the links
823  for(auto& node : tpg->_taskflow._graph.nodes()) {
824 
825  node->_topology = tpg;
826  node->_clear_state();
827 
828  if(node->num_dependents() == 0) {
829  tpg->_sources.push_back(node.get());
830  }
831 
832  int join_counter = 0;
833  for(auto p : node->_dependents) {
834  if(p->_work.index() == Node::CONDITION_WORK) {
835  node->_set_state(Node::BRANCH);
836  }
837  else {
838  join_counter++;
839  }
840  }
841 
842  node->_join_counter.store(join_counter, std::memory_order_relaxed);
843  }
844 
845  tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed);
846 }
847 
848 // Function: _tear_down_topology
849 inline void Executor::_tear_down_topology(Topology** tpg) {
850 
851  auto &f = (*tpg)->_taskflow;
852 
853  //assert(&tpg == &(f._topologies.front()));
854 
855  // case 1: we still need to run the topology again
856  if(!std::invoke((*tpg)->_pred)) {
857  //tpg->_recover_num_sinks();
858 
859  assert((*tpg)->_join_counter == 0);
860  (*tpg)->_join_counter = (*tpg)->_sources.size();
861 
862  _schedule((*tpg)->_sources);
863  }
864  // case 2: the final run of this topology
865  else {
866 
867  if((*tpg)->_call != nullptr) {
868  std::invoke((*tpg)->_call);
869  }
870 
871  f._mtx.lock();
872 
873  // If there is another run (interleave between lock)
874  if(f._topologies.size() > 1) {
875 
876  assert((*tpg)->_join_counter == 0);
877 
878  // Set the promise
879  (*tpg)->_promise.set_value();
880  f._topologies.pop_front();
881  f._mtx.unlock();
882 
883  // decrement the topology but since this is not the last we don't notify
884  _decrement_topology();
885 
886  *tpg = &(f._topologies.front());
887 
888  _set_up_topology(*tpg);
889  _schedule((*tpg)->_sources);
890 
891  //f._topologies.front()._bind(f._graph);
892  //*tpg = &(f._topologies.front());
893 
894  //assert(f._topologies.front()._join_counter == 0);
895 
896  //f._topologies.front()._join_counter = f._topologies.front()._sources.size();
897 
898  //_schedule(f._topologies.front()._sources);
899  }
900  else {
901  assert(f._topologies.size() == 1);
902 
903  // Need to back up the promise first here becuz taskflow might be
904  // destroy before taskflow leaves
905  auto p {std::move((*tpg)->_promise)};
906 
907  f._topologies.pop_front();
908 
909  f._mtx.unlock();
910 
911  // We set the promise in the end in case taskflow leaves before taskflow
912  p.set_value();
913 
914  _decrement_topology_and_notify();
915 
916  // Reset topology so caller can stop execution
917  *tpg = nullptr;
918  }
919  }
920 }
921 
922 // Function: run_until
923 template <typename P, typename C>
925 
926  // Predicate must return a boolean value
927  static_assert(std::is_invocable_v<C> && std::is_invocable_v<P>);
928 
929  _increment_topology();
930 
931  // Special case of predicate
932  if(f.empty() || std::invoke(pred)) {
933  std::promise<void> promise;
934  promise.set_value();
935  _decrement_topology_and_notify();
936  return promise.get_future();
937  }
938 
939 
940 
944  //if(_workers.size() == 0) {
945  //
946  // Topology tpg(f, std::forward<P>(pred), std::forward<C>(c));
947 
948  // // Clear last execution data & Build precedence between nodes and target
949  // tpg._bind(f._graph);
950 
951  // std::stack<Node*> stack;
952 
953  // do {
954  // _schedule_unsync(tpg._sources, stack);
955  // while(!stack.empty()) {
956  // auto node = stack.top();
957  // stack.pop();
958  // _invoke_unsync(node, stack);
959  // }
960  // tpg._recover_num_sinks();
961  // } while(!std::invoke(tpg._pred));
962 
963  // if(tpg._call != nullptr) {
964  // std::invoke(tpg._call);
965  // }
966 
967  // tpg._promise.set_value();
968  //
969  // _decrement_topology_and_notify();
970  //
971  // return tpg._promise.get_future();
972  //}
973 
974  // Multi-threaded execution.
975  bool run_now {false};
976  Topology* tpg;
977  std::future<void> future;
978 
979  {
980  std::scoped_lock lock(f._mtx);
981 
982  // create a topology for this run
983  tpg = &(f._topologies.emplace_back(f, std::forward<P>(pred), std::forward<C>(c)));
984  future = tpg->_promise.get_future();
985 
986  if(f._topologies.size() == 1) {
987  run_now = true;
988  //tpg->_bind(f._graph);
989  //_schedule(tpg->_sources);
990  }
991  }
992 
993  // Notice here calling schedule may cause the topology to be removed sonner
994  // before the function leaves.
995  if(run_now) {
996  _set_up_topology(tpg);
997  _schedule(tpg->_sources);
998  }
999 
1000  return future;
1001 }
1002 
1003 // Procedure: _increment_topology
1004 inline void Executor::_increment_topology() {
1005  std::scoped_lock<std::mutex> lock(_topology_mutex);
1006  ++_num_topologies;
1007 }
1008 
1009 // Procedure: _decrement_topology_and_notify
1010 inline void Executor::_decrement_topology_and_notify() {
1011  std::scoped_lock<std::mutex> lock(_topology_mutex);
1012  if(--_num_topologies == 0) {
1013  _topology_cv.notify_all();
1014  }
1015 }
1016 
1017 // Procedure: _decrement_topology
1018 inline void Executor::_decrement_topology() {
1019  std::scoped_lock lock(_topology_mutex);
1020  --_num_topologies;
1021 }
1022 
1023 // Procedure: wait_for_all
1024 inline void Executor::wait_for_all() {
1025  std::unique_lock lock(_topology_mutex);
1026  _topology_cv.wait(lock, [&](){ return _num_topologies == 0; });
1027 }
1028 
1029 
1030 // Procedure: _set_up_module_node
1031 inline void Executor::_set_up_module_node(Node* node) {
1032 
1033  node->_work = [node=node, this] () {
1034 
1035  // second time to enter this context
1036  if(node->_has_state(Node::SPAWNED)) {
1037  return ;
1038  }
1039 
1040  // first time to enter this context
1041  node->_set_state(Node::SPAWNED);
1042 
1043  PassiveVector<Node*> src;
1044 
1045  for(auto& n: node->_module->_graph.nodes()) {
1046 
1047  n->_topology = node->_topology;
1048  n->_parent = node;
1049  n->_set_up_join_counter();
1050 
1051  if(n->num_dependents() == 0) {
1052  src.push_back(n.get());
1053  }
1054  }
1055 
1056  node->_join_counter.fetch_add(src.size());
1057 
1058  //auto worker = _per_thread().worker;
1059  //
1060  //if(worker != nullptr) {
1061  if(node->_parent == nullptr) {
1062  node->_topology->_join_counter.fetch_add(1);
1063  }
1064  else {
1065  node->_parent->_join_counter.fetch_add(1);
1066  }
1067  //}
1069  //else {
1070  // node->_topology->_join_counter.fetch_add(src.size());
1071  //}
1072 
1073  // TODO: error if src is empty?
1074  _schedule(src);
1075  };
1076 }
1077 
1078 } // end of namespace tf -----------------------------------------------------
1079 
1080 
std::future< void > run(Taskflow &taskflow)
runs the taskflow once
Definition: executor.hpp:789
void remove_observer()
removes the associated observer
Definition: executor.hpp:531
bool empty() const noexcept
queries if the queue is empty at the time of this call
Definition: wsq.hpp:175
std::future< void > run_until(Taskflow &taskflow, P &&pred)
runs the taskflow multiple times until the predicate becomes true and then invokes a callback ...
Definition: executor.hpp:813
~Executor()
destructs the executor
Definition: executor.hpp:224
T yield(T... args)
void push(O &&item)
inserts an item to the queue
Definition: wsq.hpp:192
Definition: taskflow.hpp:5
T hardware_concurrency(T... args)
bool detached() const
queries if the subflow will be detached from its parent task
Definition: flow_builder.hpp:346
Observer * make_observer(Args &&... args)
constructs an observer to inspect the activities of worker threads
Definition: executor.hpp:522
std::optional< unsigned > this_worker_id() const
queries the id of the caller thread in this executor
Definition: executor.hpp:250
the class to create a task dependency graph
Definition: core/taskflow.hpp:18
an immutable accessor class to a task node, mainly used in the tf::ExecutorObserver interface...
Definition: task.hpp:296
bool empty() const
queries the emptiness of the taskflow
Definition: core/taskflow.hpp:139
bool joined() const
queries if the subflow will join its parent task
Definition: flow_builder.hpp:351
Lock-free unbounded single-producer multiple-consumer queue.
Definition: wsq.hpp:27
The executor class to run a taskflow graph.
Definition: executor.hpp:33
size_t num_workers() const
queries the number of worker threads (can be zero)
Definition: executor.hpp:239
std::optional< T > steal()
steals an item from the queue
Definition: wsq.hpp:242
Executor(unsigned n=std::thread::hardware_concurrency())
constructs the executor with N worker threads
Definition: executor.hpp:211
The building blocks of dynamic tasking.
Definition: flow_builder.hpp:294
std::future< void > run_n(Taskflow &taskflow, size_t N)
runs the taskflow for N times
Definition: executor.hpp:801
void wait_for_all()
wait for all pending graphs to complete
Definition: executor.hpp:1024

Back | FazBrowse Home | New Git URL