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