1d86ed7fbStbbdev /*
2*b15aabb3Stbbdev     Copyright (c) 2005-2021 Intel Corporation
3d86ed7fbStbbdev 
4d86ed7fbStbbdev     Licensed under the Apache License, Version 2.0 (the "License");
5d86ed7fbStbbdev     you may not use this file except in compliance with the License.
6d86ed7fbStbbdev     You may obtain a copy of the License at
7d86ed7fbStbbdev 
8d86ed7fbStbbdev         http://www.apache.org/licenses/LICENSE-2.0
9d86ed7fbStbbdev 
10d86ed7fbStbbdev     Unless required by applicable law or agreed to in writing, software
11d86ed7fbStbbdev     distributed under the License is distributed on an "AS IS" BASIS,
12d86ed7fbStbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d86ed7fbStbbdev     See the License for the specific language governing permissions and
14d86ed7fbStbbdev     limitations under the License.
15d86ed7fbStbbdev */
16d86ed7fbStbbdev 
17d86ed7fbStbbdev #if _MSC_VER
18d86ed7fbStbbdev // Suppress "decorated name length exceeded, name was truncated" warning
19d86ed7fbStbbdev #pragma warning(disable : 4503)
20d86ed7fbStbbdev #endif
21d86ed7fbStbbdev 
22d86ed7fbStbbdev #include <cstdlib>
23d86ed7fbStbbdev #include <cstdio>
24d86ed7fbStbbdev 
25d86ed7fbStbbdev #include <iostream>
26d86ed7fbStbbdev #include <thread>
27d86ed7fbStbbdev #include <chrono>
28d86ed7fbStbbdev #include <tuple>
29d86ed7fbStbbdev 
30d86ed7fbStbbdev #include "oneapi/tbb/flow_graph.h"
31d86ed7fbStbbdev #include "oneapi/tbb/tick_count.h"
32d86ed7fbStbbdev #include "oneapi/tbb/spin_mutex.h"
33d86ed7fbStbbdev #include "oneapi/tbb/global_control.h"
34d86ed7fbStbbdev 
35d86ed7fbStbbdev #include "common/utility/utility.hpp"
36d86ed7fbStbbdev #include "common/utility/get_default_num_threads.hpp"
37d86ed7fbStbbdev 
38d86ed7fbStbbdev // Each philosopher is an object, and is invoked in the think() function_node, the
39d86ed7fbStbbdev // eat() function_node and forward() multifunction_node.
40d86ed7fbStbbdev //
41d86ed7fbStbbdev // The graph is constructed, and each think() function_node is started with a continue_msg.
42d86ed7fbStbbdev //
43d86ed7fbStbbdev // The philosopher will think, then gather two chopsticks, eat, place the chopsticks back,
44d86ed7fbStbbdev // and if they have not completed the required number of cycles, will start to think() again
45d86ed7fbStbbdev // by sending a continue_msg to their corresponding think() function_node.
46d86ed7fbStbbdev //
47d86ed7fbStbbdev // The reserving join has as its inputs the left and right chopstick queues an a queue
48d86ed7fbStbbdev // that stores the continue_msg emitted by the function_node after think()ing is done.
49d86ed7fbStbbdev // When all three inputs are available, a tuple of the inputs will be forwarded to the
50d86ed7fbStbbdev // eat() function_node.  The output of the eat() function_node is sent to the forward()
51d86ed7fbStbbdev // multifunction_node.
52d86ed7fbStbbdev 
53d86ed7fbStbbdev const std::chrono::seconds think_time(1);
54d86ed7fbStbbdev const std::chrono::seconds eat_time(1);
55d86ed7fbStbbdev const int num_times = 10;
56d86ed7fbStbbdev 
57d86ed7fbStbbdev oneapi::tbb::tick_count t0;
58d86ed7fbStbbdev bool verbose = false;
59d86ed7fbStbbdev 
60d86ed7fbStbbdev const char *names[] = { "Archimedes", "Bakunin",   "Confucius",    "Democritus",  "Euclid",
61d86ed7fbStbbdev                         "Favorinus",  "Geminus",   "Heraclitus",   "Ichthyas",    "Jason of Nysa",
62d86ed7fbStbbdev                         "Kant",       "Lavrov",    "Metrocles",    "Nausiphanes", "Onatas",
63d86ed7fbStbbdev                         "Phaedrus",   "Quillot",   "Russell",      "Socrates",    "Thales",
64d86ed7fbStbbdev                         "Udayana",    "Vernadsky", "Wittgenstein", "Xenophilus",  "Yen Yuan",
65d86ed7fbStbbdev                         "Zenodotus" };
66d86ed7fbStbbdev const int NumPhilosophers = sizeof(names) / sizeof(char *);
67d86ed7fbStbbdev 
68d86ed7fbStbbdev struct RunOptions {
69d86ed7fbStbbdev     utility::thread_number_range threads;
70d86ed7fbStbbdev     int number_of_philosophers;
71d86ed7fbStbbdev     bool silent;
RunOptionsRunOptions72d86ed7fbStbbdev     RunOptions(utility::thread_number_range threads_, int number_of_philosophers_, bool silent_)
73d86ed7fbStbbdev             : threads(threads_),
74d86ed7fbStbbdev               number_of_philosophers(number_of_philosophers_),
75d86ed7fbStbbdev               silent(silent_) {}
76d86ed7fbStbbdev };
77d86ed7fbStbbdev 
ParseCommandLine(int argc,char * argv[])78d86ed7fbStbbdev RunOptions ParseCommandLine(int argc, char *argv[]) {
79d86ed7fbStbbdev     int auto_threads = utility::get_default_num_threads();
80d86ed7fbStbbdev     utility::thread_number_range threads(
81d86ed7fbStbbdev         utility::get_default_num_threads, auto_threads, auto_threads);
82d86ed7fbStbbdev     int nPhilosophers = 5;
83d86ed7fbStbbdev     bool verbose = false;
84d86ed7fbStbbdev     char charbuf[100];
85d86ed7fbStbbdev     std::sprintf(charbuf, "%d", NumPhilosophers);
86d86ed7fbStbbdev     std::string pCount = "how many philosophers, from 2-";
87d86ed7fbStbbdev     pCount += charbuf;
88d86ed7fbStbbdev 
89d86ed7fbStbbdev     utility::cli_argument_pack cli_pack;
90d86ed7fbStbbdev     cli_pack.positional_arg(threads, "n-of_threads", utility::thread_number_range_desc)
91d86ed7fbStbbdev         .positional_arg(nPhilosophers, "n-of-philosophers", pCount)
92d86ed7fbStbbdev         .arg(verbose, "verbose", "verbose output");
93d86ed7fbStbbdev     utility::parse_cli_arguments(argc, argv, cli_pack);
94d86ed7fbStbbdev     if (nPhilosophers < 2 || nPhilosophers > NumPhilosophers) {
95d86ed7fbStbbdev         std::cout << "Number of philosophers (" << nPhilosophers
96d86ed7fbStbbdev                   << ") out of range [2:" << NumPhilosophers << "]\n";
97d86ed7fbStbbdev         std::cout << cli_pack.usage_string(argv[0]) << std::flush;
98d86ed7fbStbbdev         std::exit(-1);
99d86ed7fbStbbdev     }
100d86ed7fbStbbdev     return RunOptions(threads, nPhilosophers, !verbose);
101d86ed7fbStbbdev }
102d86ed7fbStbbdev 
103d86ed7fbStbbdev oneapi::tbb::spin_mutex my_mutex;
104d86ed7fbStbbdev 
105d86ed7fbStbbdev class chopstick {};
106d86ed7fbStbbdev 
107d86ed7fbStbbdev typedef std::tuple<oneapi::tbb::flow::continue_msg, chopstick, chopstick> join_output;
108d86ed7fbStbbdev typedef oneapi::tbb::flow::join_node<join_output, oneapi::tbb::flow::reserving> join_node_type;
109d86ed7fbStbbdev 
110d86ed7fbStbbdev typedef oneapi::tbb::flow::function_node<oneapi::tbb::flow::continue_msg,
111d86ed7fbStbbdev                                          oneapi::tbb::flow::continue_msg>
112d86ed7fbStbbdev     think_node_type;
113d86ed7fbStbbdev typedef oneapi::tbb::flow::function_node<join_output, oneapi::tbb::flow::continue_msg>
114d86ed7fbStbbdev     eat_node_type;
115d86ed7fbStbbdev typedef oneapi::tbb::flow::multifunction_node<oneapi::tbb::flow::continue_msg, join_output>
116d86ed7fbStbbdev     forward_node_type;
117d86ed7fbStbbdev 
118d86ed7fbStbbdev class philosopher {
119d86ed7fbStbbdev public:
philosopher(const char * name)120d86ed7fbStbbdev     philosopher(const char *name) : my_name(name), my_count(num_times) {}
121d86ed7fbStbbdev 
~philosopher()122d86ed7fbStbbdev     ~philosopher() {}
123d86ed7fbStbbdev 
124d86ed7fbStbbdev     void check();
name() const125d86ed7fbStbbdev     const char *name() const {
126d86ed7fbStbbdev         return my_name;
127d86ed7fbStbbdev     }
128d86ed7fbStbbdev 
129d86ed7fbStbbdev private:
130d86ed7fbStbbdev     friend std::ostream &operator<<(std::ostream &o, philosopher const &p);
131d86ed7fbStbbdev 
132d86ed7fbStbbdev     const char *my_name;
133d86ed7fbStbbdev     int my_count;
134d86ed7fbStbbdev 
135d86ed7fbStbbdev     friend class think_node_body;
136d86ed7fbStbbdev     friend class eat_node_body;
137d86ed7fbStbbdev     friend class forward_node_body;
138d86ed7fbStbbdev 
139d86ed7fbStbbdev     void think();
140d86ed7fbStbbdev     void eat();
141d86ed7fbStbbdev     void forward(const oneapi::tbb::flow::continue_msg &in,
142d86ed7fbStbbdev                  forward_node_type::output_ports_type &out_ports);
143d86ed7fbStbbdev };
144d86ed7fbStbbdev 
operator <<(std::ostream & o,philosopher const & p)145d86ed7fbStbbdev std::ostream &operator<<(std::ostream &o, philosopher const &p) {
146d86ed7fbStbbdev     o << "< philosopher[" << reinterpret_cast<uintptr_t>(const_cast<philosopher *>(&p)) << "] "
147d86ed7fbStbbdev       << p.name() << ", my_count=" << p.my_count;
148d86ed7fbStbbdev 
149d86ed7fbStbbdev     return o;
150d86ed7fbStbbdev }
151d86ed7fbStbbdev 
152d86ed7fbStbbdev class think_node_body {
153d86ed7fbStbbdev     philosopher &my_philosopher;
154d86ed7fbStbbdev 
155d86ed7fbStbbdev public:
think_node_body(philosopher & p)156d86ed7fbStbbdev     think_node_body(philosopher &p) : my_philosopher(p) {}
think_node_body(const think_node_body & other)157d86ed7fbStbbdev     think_node_body(const think_node_body &other) : my_philosopher(other.my_philosopher) {}
operator ()(oneapi::tbb::flow::continue_msg)158d86ed7fbStbbdev     oneapi::tbb::flow::continue_msg operator()(oneapi::tbb::flow::continue_msg /*m*/) {
159d86ed7fbStbbdev         my_philosopher.think();
160d86ed7fbStbbdev         return oneapi::tbb::flow::continue_msg();
161d86ed7fbStbbdev     }
162d86ed7fbStbbdev };
163d86ed7fbStbbdev 
164d86ed7fbStbbdev class eat_node_body {
165d86ed7fbStbbdev     philosopher &my_philosopher;
166d86ed7fbStbbdev 
167d86ed7fbStbbdev public:
eat_node_body(philosopher & p)168d86ed7fbStbbdev     eat_node_body(philosopher &p) : my_philosopher(p) {}
eat_node_body(const eat_node_body & other)169d86ed7fbStbbdev     eat_node_body(const eat_node_body &other) : my_philosopher(other.my_philosopher) {}
operator ()(const join_output & in)170d86ed7fbStbbdev     oneapi::tbb::flow::continue_msg operator()(const join_output &in) {
171d86ed7fbStbbdev         my_philosopher.eat();
172d86ed7fbStbbdev         return oneapi::tbb::flow::continue_msg();
173d86ed7fbStbbdev     }
174d86ed7fbStbbdev };
175d86ed7fbStbbdev 
176d86ed7fbStbbdev class forward_node_body {
177d86ed7fbStbbdev     philosopher &my_philosopher;
178d86ed7fbStbbdev 
179d86ed7fbStbbdev public:
forward_node_body(philosopher & p)180d86ed7fbStbbdev     forward_node_body(philosopher &p) : my_philosopher(p) {}
forward_node_body(const forward_node_body & other)181d86ed7fbStbbdev     forward_node_body(const forward_node_body &other) : my_philosopher(other.my_philosopher) {}
operator ()(const oneapi::tbb::flow::continue_msg & in,forward_node_type::output_ports_type & out)182d86ed7fbStbbdev     void operator()(const oneapi::tbb::flow::continue_msg &in,
183d86ed7fbStbbdev                     forward_node_type::output_ports_type &out) {
184d86ed7fbStbbdev         my_philosopher.forward(in, out);
185d86ed7fbStbbdev     }
186d86ed7fbStbbdev };
187d86ed7fbStbbdev 
check()188d86ed7fbStbbdev void philosopher::check() {
189d86ed7fbStbbdev     if (my_count != 0) {
190d86ed7fbStbbdev         std::printf("ERROR: philosopher %s still had to run %d more times\n", name(), my_count);
191d86ed7fbStbbdev         std::exit(-1);
192d86ed7fbStbbdev     }
193d86ed7fbStbbdev }
194d86ed7fbStbbdev 
forward(const oneapi::tbb::flow::continue_msg &,forward_node_type::output_ports_type & out_ports)195d86ed7fbStbbdev void philosopher::forward(const oneapi::tbb::flow::continue_msg & /*in*/,
196d86ed7fbStbbdev                           forward_node_type::output_ports_type &out_ports) {
197d86ed7fbStbbdev     if (my_count < 0)
198d86ed7fbStbbdev         abort();
199d86ed7fbStbbdev     --my_count;
200d86ed7fbStbbdev     (void)std::get<1>(out_ports).try_put(chopstick());
201d86ed7fbStbbdev     (void)std::get<2>(out_ports).try_put(chopstick());
202d86ed7fbStbbdev     if (my_count > 0) {
203d86ed7fbStbbdev         (void)std::get<0>(out_ports).try_put(
204d86ed7fbStbbdev             oneapi::tbb::flow::continue_msg()); //start thinking again
205d86ed7fbStbbdev     }
206d86ed7fbStbbdev     else {
207d86ed7fbStbbdev         if (verbose) {
208d86ed7fbStbbdev             oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
209d86ed7fbStbbdev             std::printf("%s has left the building\n", name());
210d86ed7fbStbbdev         }
211d86ed7fbStbbdev     }
212d86ed7fbStbbdev }
213d86ed7fbStbbdev 
eat()214d86ed7fbStbbdev void philosopher::eat() {
215d86ed7fbStbbdev     if (verbose) {
216d86ed7fbStbbdev         oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
217d86ed7fbStbbdev         std::printf("%s eating\n", name());
218d86ed7fbStbbdev     }
219d86ed7fbStbbdev     std::this_thread::sleep_for(eat_time);
220d86ed7fbStbbdev     if (verbose) {
221d86ed7fbStbbdev         oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
222d86ed7fbStbbdev         std::printf("%s done eating\n", name());
223d86ed7fbStbbdev     }
224d86ed7fbStbbdev }
225d86ed7fbStbbdev 
think()226d86ed7fbStbbdev void philosopher::think() {
227d86ed7fbStbbdev     if (verbose) {
228d86ed7fbStbbdev         oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
229d86ed7fbStbbdev         std::printf("%s thinking\n", name());
230d86ed7fbStbbdev     }
231d86ed7fbStbbdev     std::this_thread::sleep_for(think_time);
232d86ed7fbStbbdev     if (verbose) {
233d86ed7fbStbbdev         oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
234d86ed7fbStbbdev         std::printf("%s done thinking\n", name());
235d86ed7fbStbbdev     }
236d86ed7fbStbbdev }
237d86ed7fbStbbdev 
238d86ed7fbStbbdev typedef oneapi::tbb::flow::queue_node<oneapi::tbb::flow::continue_msg> thinking_done_type;
239d86ed7fbStbbdev 
main(int argc,char * argv[])240d86ed7fbStbbdev int main(int argc, char *argv[]) {
241d86ed7fbStbbdev     using oneapi::tbb::flow::make_edge;
242d86ed7fbStbbdev     using oneapi::tbb::flow::input_port;
243d86ed7fbStbbdev     using oneapi::tbb::flow::output_port;
244d86ed7fbStbbdev 
245d86ed7fbStbbdev     oneapi::tbb::tick_count main_time = oneapi::tbb::tick_count::now();
246d86ed7fbStbbdev     int num_threads;
247d86ed7fbStbbdev     int num_philosophers;
248d86ed7fbStbbdev 
249d86ed7fbStbbdev     RunOptions options = ParseCommandLine(argc, argv);
250d86ed7fbStbbdev     num_philosophers = options.number_of_philosophers;
251d86ed7fbStbbdev     verbose = !options.silent;
252d86ed7fbStbbdev 
253d86ed7fbStbbdev     for (num_threads = options.threads.first; num_threads <= options.threads.last;
254d86ed7fbStbbdev          num_threads = options.threads.step(num_threads)) {
255d86ed7fbStbbdev         oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism,
256d86ed7fbStbbdev                                       num_threads);
257d86ed7fbStbbdev 
258d86ed7fbStbbdev         oneapi::tbb::flow::graph g;
259d86ed7fbStbbdev 
260d86ed7fbStbbdev         if (verbose) {
261d86ed7fbStbbdev             std::cout << "\n"
262d86ed7fbStbbdev                       << num_philosophers << " philosophers with " << num_threads << " threads"
263d86ed7fbStbbdev                       << "\n"
264d86ed7fbStbbdev                       << "\n";
265d86ed7fbStbbdev         }
266d86ed7fbStbbdev         t0 = oneapi::tbb::tick_count::now();
267d86ed7fbStbbdev 
268d86ed7fbStbbdev         std::vector<oneapi::tbb::flow::queue_node<chopstick>> places(
269d86ed7fbStbbdev             num_philosophers, oneapi::tbb::flow::queue_node<chopstick>(g));
270d86ed7fbStbbdev         std::vector<philosopher> philosophers;
271d86ed7fbStbbdev         philosophers.reserve(num_philosophers);
272d86ed7fbStbbdev         std::vector<think_node_type *> think_nodes;
273d86ed7fbStbbdev         think_nodes.reserve(num_philosophers);
274d86ed7fbStbbdev         std::vector<thinking_done_type> done_vector(num_philosophers, thinking_done_type(g));
275d86ed7fbStbbdev         std::vector<join_node_type> join_vector(num_philosophers, join_node_type(g));
276d86ed7fbStbbdev         std::vector<eat_node_type *> eat_nodes;
277d86ed7fbStbbdev         eat_nodes.reserve(num_philosophers);
278d86ed7fbStbbdev         std::vector<forward_node_type *> forward_nodes;
279d86ed7fbStbbdev         forward_nodes.reserve(num_philosophers);
280d86ed7fbStbbdev         for (int i = 0; i < num_philosophers; ++i) {
281d86ed7fbStbbdev             places[i].try_put(chopstick());
282d86ed7fbStbbdev             philosophers.push_back(
283d86ed7fbStbbdev                 philosopher(names[i])); // allowed because of default generated assignment
284d86ed7fbStbbdev             if (verbose) {
285d86ed7fbStbbdev                 oneapi::tbb::spin_mutex::scoped_lock lock(my_mutex);
286d86ed7fbStbbdev                 std::cout << "Built philosopher " << philosophers[i] << "\n";
287d86ed7fbStbbdev             }
288d86ed7fbStbbdev             think_nodes.push_back(new think_node_type(
289d86ed7fbStbbdev                 g, oneapi::tbb::flow::unlimited, think_node_body(philosophers[i])));
290d86ed7fbStbbdev             eat_nodes.push_back(
291d86ed7fbStbbdev                 new eat_node_type(g, oneapi::tbb::flow::unlimited, eat_node_body(philosophers[i])));
292d86ed7fbStbbdev             forward_nodes.push_back(new forward_node_type(
293d86ed7fbStbbdev                 g, oneapi::tbb::flow::unlimited, forward_node_body(philosophers[i])));
294d86ed7fbStbbdev         }
295d86ed7fbStbbdev 
296d86ed7fbStbbdev         // attach chopstick buffers and think function_nodes to joins
297d86ed7fbStbbdev         for (int i = 0; i < num_philosophers; ++i) {
298d86ed7fbStbbdev             make_edge(*think_nodes[i], done_vector[i]);
299d86ed7fbStbbdev             make_edge(done_vector[i], input_port<0>(join_vector[i]));
300d86ed7fbStbbdev             make_edge(places[i], input_port<1>(join_vector[i])); // left chopstick
301d86ed7fbStbbdev             make_edge(places[(i + 1) % num_philosophers],
302d86ed7fbStbbdev                       input_port<2>(join_vector[i])); // right chopstick
303d86ed7fbStbbdev             make_edge(join_vector[i], *eat_nodes[i]);
304d86ed7fbStbbdev             make_edge(*eat_nodes[i], *forward_nodes[i]);
305d86ed7fbStbbdev             make_edge(output_port<0>(*forward_nodes[i]), *think_nodes[i]);
306d86ed7fbStbbdev             make_edge(output_port<1>(*forward_nodes[i]), places[i]);
307d86ed7fbStbbdev             make_edge(output_port<2>(*forward_nodes[i]), places[(i + 1) % num_philosophers]);
308d86ed7fbStbbdev         }
309d86ed7fbStbbdev 
310d86ed7fbStbbdev         // start all the philosophers thinking
311d86ed7fbStbbdev         for (int i = 0; i < num_philosophers; ++i)
312d86ed7fbStbbdev             think_nodes[i]->try_put(oneapi::tbb::flow::continue_msg());
313d86ed7fbStbbdev 
314d86ed7fbStbbdev         g.wait_for_all();
315d86ed7fbStbbdev 
316d86ed7fbStbbdev         oneapi::tbb::tick_count t1 = oneapi::tbb::tick_count::now();
317d86ed7fbStbbdev         if (verbose)
318d86ed7fbStbbdev             std::cout << "\n"
319d86ed7fbStbbdev                       << num_philosophers << " philosophers with " << num_threads
320d86ed7fbStbbdev                       << " threads have taken " << (t1 - t0).seconds() << "seconds"
321d86ed7fbStbbdev                       << "\n";
322d86ed7fbStbbdev 
323d86ed7fbStbbdev         for (int i = 0; i < num_philosophers; ++i)
324d86ed7fbStbbdev             philosophers[i].check();
325d86ed7fbStbbdev 
326d86ed7fbStbbdev         for (int i = 0; i < num_philosophers; ++i) {
327d86ed7fbStbbdev             delete think_nodes[i];
328d86ed7fbStbbdev             delete eat_nodes[i];
329d86ed7fbStbbdev             delete forward_nodes[i];
330d86ed7fbStbbdev         }
331d86ed7fbStbbdev     }
332d86ed7fbStbbdev 
333d86ed7fbStbbdev     utility::report_elapsed_time((oneapi::tbb::tick_count::now() - main_time).seconds());
334d86ed7fbStbbdev     return 0;
335d86ed7fbStbbdev }
336