1 // 2 // libsemigroups - C++ library for semigroups and monoids 3 // Copyright (C) 2019 James D. Mitchell 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 // 18 19 // This file contains the declaration of the Race class template for 20 // competitively running different functions/methods in different threads, and 21 // obtaining the winner. 22 23 // TODO(later): 24 // 1. consider if keeping killed off methods has any uses 25 // 2. update run_until to be similar to Runner::run_until 26 27 #ifndef LIBSEMIGROUPS_RACE_HPP_ 28 #define LIBSEMIGROUPS_RACE_HPP_ 29 30 #include <chrono> // for nanoseconds 31 #include <cstddef> // for size_t 32 #include <memory> // for std::shared_ptr 33 #include <thread> // for mutex 34 #include <vector> // for vector 35 36 #include "libsemigroups-debug.hpp" // for LIBSEMIGROUPS_ASSERT 37 #include "libsemigroups-exception.hpp" // for LIBSEMIGROUPS_EXCEPTION 38 #include "report.hpp" // for REPORT_DEFAULT, REPORT_TIME 39 #include "runner.hpp" // for Runner 40 #include "stl.hpp" // for IsCallable 41 #include "timer.hpp" // for Timer 42 43 namespace libsemigroups { 44 namespace detail { 45 class Race final { 46 public: 47 // Construct an empty Race object, with maximum number of threads set to 48 // std::thread::hardware_concurrency. 49 Race(); Race(Race const & other)50 Race(Race const& other) : Race() { 51 // Can't use = default because std::mutex is non-copyable. 52 _runners = other._runners; 53 _max_threads = other._max_threads; 54 _winner = other._winner; 55 } 56 57 Race(Race&&) = delete; 58 Race& operator=(Race const&) = delete; 59 Race& operator=(Race&&) = delete; 60 61 ~Race(); 62 63 // Set the maximum number of threads, throws if try to set to 0. max_threads(size_t val)64 Race& max_threads(size_t val) noexcept { 65 LIBSEMIGROUPS_ASSERT(val != 0); 66 _max_threads = val; 67 return *this; 68 } 69 max_threads() const70 size_t max_threads() const noexcept { 71 return _max_threads; 72 } 73 74 // Runs the method Runner::run on every Runner in the Race, and returns 75 // the one that finishes first. The losers are deleted. winner()76 std::shared_ptr<Runner> winner() { 77 run(); 78 return _winner; 79 } 80 finished() const81 bool finished() const noexcept { 82 return _winner != nullptr; 83 } 84 85 // Adds a Runner to the race, throws if the race is already over. 86 void add_runner(std::shared_ptr<Runner>); 87 88 using const_iterator = 89 typename std::vector<std::shared_ptr<Runner>>::const_iterator; 90 begin() const91 const_iterator begin() const noexcept { 92 return _runners.cbegin(); 93 } 94 end() const95 const_iterator end() const noexcept { 96 return _runners.cend(); 97 } 98 // Returns an iterator pointing to the first Runner in the Race. cbegin() const99 const_iterator cbegin() const noexcept { 100 return _runners.cbegin(); 101 } 102 103 // Returns an iterator pointing to one past the last Runner in the Race. cend() const104 const_iterator cend() const noexcept { 105 return _runners.cend(); 106 } 107 108 // Returns \c true if there are no Runners in the race, and \c false 109 // otherwise. 110 // std::vector::empty is noexcept empty() const111 bool empty() const noexcept { 112 return _runners.empty(); 113 } 114 115 // std::vector::size is noexcept number_runners() const116 size_t number_runners() const noexcept { 117 return _runners.size(); 118 } 119 120 // Runs the race to completion. 121 void run(); 122 123 // Runs the race for the specified amount of time. 124 void run_for(std::chrono::nanoseconds); 125 126 // Runs until \p func returns \c true, or the race is over. This 127 // repeatedly calls Race::run_for for \p check_interval, and then checks 128 // whether or not \p func() returns true. The object \p func can be any 129 // callable object with 0 parameters and that returns a bool. 130 // This is definitely tested but doesn't show up in the code coverage for 131 // some reason. 132 template <typename TCallable> run_until(TCallable const & func,std::chrono::nanoseconds check_interval=std::chrono::milliseconds (2))133 void run_until(TCallable const& func, 134 std::chrono::nanoseconds check_interval 135 = std::chrono::milliseconds(2)) { 136 static_assert(detail::IsCallable<TCallable>::value, 137 "the template parameter TCallable must be callable"); 138 static_assert(std::is_same<typename std::result_of<TCallable()>::type, 139 bool>::value, 140 "the template parameter TCallable must return a bool"); 141 if (empty()) { 142 LIBSEMIGROUPS_EXCEPTION("no runners given, cannot run_until"); 143 } 144 while (!func() && _winner == nullptr) { 145 // if _winner != nullptr, then the race is over. 146 run_for(check_interval); 147 check_interval *= 2; 148 } 149 } 150 151 template <typename T> find_runner() const152 std::shared_ptr<T> find_runner() const { 153 static_assert(std::is_base_of<Runner, T>::value, 154 "the template parameter must be derived from Runner"); 155 // We use find_if so that this works even if we haven't computed 156 // anything at all. 157 auto it = std::find_if(_runners.begin(), 158 _runners.end(), 159 [](std::shared_ptr<Runner> const& m) { 160 auto& r = *(m.get()); 161 return typeid(r) == typeid(T); 162 }); 163 if (it != _runners.end()) { 164 return std::static_pointer_cast<T>(*it); 165 } else { 166 return nullptr; 167 } 168 } 169 170 private: 171 // Runs the callable object \p func on every Runner in parallel. 172 template <typename TCallable> run_func(TCallable const & func)173 void run_func(TCallable const& func) { 174 static_assert(std::is_same<typename std::result_of<TCallable( 175 std::shared_ptr<Runner>)>::type, 176 void>::value, 177 "the result of calling the template parameter TCallable " 178 "must be void"); 179 LIBSEMIGROUPS_ASSERT(!empty()); 180 if (_winner == nullptr) { 181 size_t nr_threads = std::min(_runners.size(), _max_threads); 182 if (nr_threads == 1) { 183 REPORT_DEFAULT("using 0 additional threads\n"); 184 detail::Timer tmr; 185 func(_runners.at(0)); 186 _winner = _runners.at(0); 187 REPORT_TIME(tmr); 188 return; 189 } 190 for (size_t i = 0; i < _runners.size(); ++i) { 191 if (_runners[i]->finished()) { 192 REPORT_DEFAULT("using 0 additional threads\n"); 193 _winner = _runners[i]; 194 REPORT_DEFAULT("#%d is already finished!\n", i); 195 return; 196 } 197 } 198 199 std::vector<std::thread::id> tids(_runners.size(), 200 std::this_thread::get_id()); 201 202 REPORT_DEFAULT("using %d / %d additional threads\n", 203 nr_threads, 204 std::thread::hardware_concurrency()); 205 detail::Timer tmr; 206 LIBSEMIGROUPS_ASSERT(nr_threads != 0); 207 208 auto thread_func = [this, &func, &tids](size_t pos) { 209 tids[pos] = std::this_thread::get_id(); 210 try { 211 func(_runners.at(pos)); 212 } catch (std::exception const& e) { 213 size_t tid = THREAD_ID_MANAGER.tid(tids[pos]); 214 REPORT_DEFAULT("exception thrown by #%d:\n%s\n", tid, e.what()); 215 return; 216 } 217 // Stop two Runner* objects from killing each other 218 { 219 std::lock_guard<std::mutex> lg(_mtx); 220 if (_runners.at(pos)->finished()) { 221 for (auto it = _runners.begin(); it < _runners.begin() + pos; 222 it++) { 223 (*it)->kill(); 224 } 225 for (auto it = _runners.begin() + pos + 1; it < _runners.end(); 226 it++) { 227 (*it)->kill(); 228 } 229 } 230 } 231 }; 232 233 THREAD_ID_MANAGER.reset(); 234 235 std::vector<std::thread> t; 236 for (size_t i = 0; i < nr_threads; ++i) { 237 t.push_back(std::thread(thread_func, i)); 238 } 239 for (size_t i = 0; i < nr_threads; ++i) { 240 t.at(i).join(); 241 } 242 REPORT_TIME(tmr); 243 for (auto method = _runners.begin(); method < _runners.end(); 244 ++method) { 245 if ((*method)->finished()) { 246 LIBSEMIGROUPS_ASSERT(_winner == nullptr); 247 _winner = *method; 248 size_t tid 249 = THREAD_ID_MANAGER.tid(tids.at(method - _runners.begin())); 250 REPORT_DEFAULT("#%d is the winner!\n", tid); 251 break; 252 } 253 } 254 if (_winner != nullptr) { 255 for (auto rnnr : _runners) { 256 if (rnnr != _winner) { 257 rnnr.reset(); 258 } 259 } 260 _runners.clear(); 261 _runners.push_back(_winner); 262 } 263 } 264 } 265 266 std::vector<std::shared_ptr<Runner>> _runners; 267 size_t _max_threads; 268 std::mutex _mtx; 269 std::shared_ptr<Runner> _winner; 270 }; 271 } // namespace detail 272 } // namespace libsemigroups 273 274 #endif // LIBSEMIGROUPS_RACE_HPP_ 275